diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 7451f0743..fdc0dbe9f 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -22,6 +22,8 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/message_view_header.xml b/res/layout/message_view_header.xml
index 84aede057..f0dbd7561 100644
--- a/res/layout/message_view_header.xml
+++ b/res/layout/message_view_header.xml
@@ -17,7 +17,7 @@
android:layout_height="wrap_content"
android:orientation="vertical"
>
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
500件
1000件
-
サーバーと同期されていないメールをコピーまたは移動できません
設定エラー
@@ -886,7 +885,6 @@ K-9 Mail セットアップにようこそ。\nK-9 はオープンソースで
メッセージの下書き保存
メッセージを保存しますか?
-
このメッセージに使われている文字セット \"%s\" は存在していません
選択したテキストをコピーします
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index 9831c06c8..0c6da49f5 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -589,4 +589,14 @@
- 5
+
+ - @string/account_settings_crypto_app_none
+ - APG
+
+
+
+
+ - apg
+
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 9a15643ef..bb3e725e0 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -512,6 +512,12 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin
Listing messages
Viewing messages
Quote prefix
+ Cryptography
+ OpenPGP Provider
+ None
+ not available
+ Auto-sign
+ Use the account\'s email address to guess the signature key.
Folder poll check frequency
2nd class check frequency
@@ -891,6 +897,20 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin
Use Gallery bug work-around
Show buttons to add image/video attachments (to work around a Gallery 3D bug)
+
+ No suitable application for this action found.
+ The installed APG version is not supported.
+ Sign
+ Encrypt
+ Decrypt
+ Verify
+ <unknown>
+ id: %s
+ K-9 doesn\'t have permission to access APG fully, please reinstall K-9 to fix that.
+ PGP/MIME messages are not supported yet.
+ Warning: attachments are NOT signed or encrypted yet.
+ Send aborted.
+
Save draft message?
Save or Discard this message?
diff --git a/res/xml/account_settings_preferences.xml b/res/xml/account_settings_preferences.xml
index d9ae8eba4..a48b89b48 100644
--- a/res/xml/account_settings_preferences.xml
+++ b/res/xml/account_settings_preferences.xml
@@ -207,6 +207,22 @@
+
+
+
+
+
+
+
0)
+ {
+ mCrypto.setSignatureKeyId(ids[0]);
+ mCrypto.setSignatureUserId(mCrypto.getUserId(this, ids[0]));
+ }
+ else
+ {
+ mCrypto.setSignatureKeyId(0);
+ mCrypto.setSignatureUserId(null);
+ }
+ }
+ updateEncryptLayout();
+ }
+ else
+ {
+ mEncryptLayout.setVisibility(View.GONE);
+ }
+
mDraftNeedsSaving = false;
}
+ private void initializeCrypto()
+ {
+ if (mCrypto != null)
+ {
+ return;
+ }
+ mCrypto = CryptoProvider.createInstance(mAccount);
+ }
+
+ /**
+ * Fill the encrypt layout with the latest data about signature key and encryption keys.
+ */
+ public void updateEncryptLayout()
+ {
+ if (!mCrypto.hasSignatureKey())
+ {
+ mCryptoSignatureCheckbox.setText(R.string.btn_crypto_sign);
+ mCryptoSignatureCheckbox.setChecked(false);
+ mCryptoSignatureUserId.setVisibility(View.INVISIBLE);
+ mCryptoSignatureUserIdRest.setVisibility(View.INVISIBLE);
+ }
+ else
+ {
+ // if a signature key is selected, then the checkbox itself has no text
+ mCryptoSignatureCheckbox.setText("");
+ mCryptoSignatureCheckbox.setChecked(true);
+ mCryptoSignatureUserId.setVisibility(View.VISIBLE);
+ mCryptoSignatureUserIdRest.setVisibility(View.VISIBLE);
+ mCryptoSignatureUserId.setText(R.string.unknown_crypto_signature_user_id);
+ mCryptoSignatureUserIdRest.setText("");
+
+ String userId = mCrypto.getSignatureUserId();
+ if (userId == null)
+ {
+ userId = mCrypto.getUserId(this, mCrypto.getSignatureKeyId());
+ mCrypto.setSignatureUserId(userId);
+ }
+
+ if (userId != null)
+ {
+ String chunks[] = mCrypto.getSignatureUserId().split(" <", 2);
+ mCryptoSignatureUserId.setText(chunks[0]);
+ if (chunks.length > 1)
+ {
+ mCryptoSignatureUserIdRest.setText("<" + chunks[1]);
+ }
+ }
+ }
+ }
+
@Override
public void onResume()
{
@@ -659,6 +810,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
outState.putString(STATE_KEY_DRAFT_UID, mDraftUid);
outState.putSerializable(STATE_IDENTITY, mIdentity);
outState.putBoolean(STATE_IDENTITY_CHANGED, mIdentityChanged);
+ outState.putSerializable(STATE_CRYPTO, mCrypto);
outState.putString(STATE_IN_REPLY_TO, mInReplyTo);
outState.putString(STATE_REFERENCES, mReferences);
}
@@ -682,10 +834,14 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
mDraftUid = savedInstanceState.getString(STATE_KEY_DRAFT_UID);
mIdentity = (Identity)savedInstanceState.getSerializable(STATE_IDENTITY);
mIdentityChanged = savedInstanceState.getBoolean(STATE_IDENTITY_CHANGED);
+ mCrypto = (CryptoProvider) savedInstanceState.getSerializable(STATE_CRYPTO);
mInReplyTo = savedInstanceState.getString(STATE_IN_REPLY_TO);
mReferences = savedInstanceState.getString(STATE_REFERENCES);
+
+ initializeCrypto();
updateFrom();
updateSignature();
+ updateEncryptLayout();
mDraftNeedsSaving = false;
}
@@ -733,6 +889,32 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
return addresses;
}
+ private String buildText(boolean appendSig)
+ {
+ /*
+ * Build the Body that will contain the text of the message. We'll decide where to
+ * include it later.
+ */
+
+ String text = mMessageContentView.getText().toString();
+ if (appendSig && mAccount.isSignatureBeforeQuotedText())
+ {
+ text = appendSignature(text);
+ }
+
+ if (mQuotedTextBar.getVisibility() == View.VISIBLE)
+ {
+ text += "\n" + mQuotedText.getText().toString();
+ }
+
+ if (appendSig && mAccount.isSignatureBeforeQuotedText() == false)
+ {
+ text = appendSignature(text);
+ }
+
+ return text;
+ }
+
private MimeMessage createMessage(boolean appendSig) throws MessagingException
{
MimeMessage message = new MimeMessage();
@@ -761,27 +943,14 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
message.setReferences(mReferences);
}
-
- /*
- * Build the Body that will contain the text of the message. We'll decide where to
- * include it later.
- */
-
- String text = mMessageContentView.getText().toString();
- if (appendSig && mAccount.isSignatureBeforeQuotedText())
+ String text = null;
+ if (mCrypto.getEncryptedData() != null)
{
- text = appendSignature(text);
+ text = mCrypto.getEncryptedData();
}
-
- if (mQuotedTextBar.getVisibility() == View.VISIBLE)
+ else
{
- text += "\n" + mQuotedText.getText().toString();
- }
-
-
- if (appendSig && mAccount.isSignatureBeforeQuotedText() == false)
- {
- text = appendSignature(text);
+ text = buildText(appendSig);
}
TextBody body = new TextBody(text);
@@ -869,7 +1038,6 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
}
-
private void sendMessage()
{
new SendMessageTask().execute();
@@ -881,10 +1049,37 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
private void saveIfNeeded()
{
- if (!mDraftNeedsSaving)
+ if (!mDraftNeedsSaving || mPreventDraftSaving || mCrypto.hasEncryptionKeys())
{
return;
}
+
+ mDraftNeedsSaving = false;
+ saveMessage();
+ }
+
+ public void onEncryptionKeySelectionDone()
+ {
+ if (mCrypto.hasEncryptionKeys())
+ {
+ onSend();
+ }
+ else
+ {
+ Toast.makeText(this, R.string.send_aborted, Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ public void onEncryptDone()
+ {
+ if (mCrypto.getEncryptedData() != null)
+ {
+ onSend();
+ }
+ else
+ {
+ Toast.makeText(this, R.string.send_aborted, Toast.LENGTH_SHORT).show();
+ }
mDraftNeedsSaving = false;
saveMessage();
}
@@ -897,6 +1092,51 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
Toast.makeText(this, getString(R.string.message_compose_error_no_recipients), Toast.LENGTH_LONG).show();
return;
}
+ if (mEncryptCheckbox.isChecked() && !mCrypto.hasEncryptionKeys())
+ {
+ // key selection before encryption
+ String emails = "";
+ Address[][] addresses = new Address[][] { getAddresses(mToView),
+ getAddresses(mCcView),
+ getAddresses(mBccView)
+ };
+ for (Address[] addressArray : addresses)
+ {
+ for (Address address : addressArray)
+ {
+ if (emails.length() != 0)
+ {
+ emails += ",";
+ }
+ emails += address.getAddress();
+ }
+ }
+ if (emails.length() != 0)
+ {
+ emails += ",";
+ }
+ emails += mIdentity.getEmail();
+
+ mPreventDraftSaving = true;
+ if (!mCrypto.selectEncryptionKeys(MessageCompose.this, emails))
+ {
+ mPreventDraftSaving = false;
+ }
+ return;
+ }
+ if (mCrypto.hasEncryptionKeys() || mCrypto.hasSignatureKey())
+ {
+ if (mCrypto.getEncryptedData() == null)
+ {
+ String text = buildText(true);
+ mPreventDraftSaving = true;
+ if (!mCrypto.encrypt(this, text))
+ {
+ mPreventDraftSaving = false;
+ }
+ return;
+ }
+ }
sendMessage();
mDraftNeedsSaving = false;
finish();
@@ -956,6 +1196,10 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
*/
private void onAddAttachment2(final String mime_type)
{
+ if (mCrypto.isAvailable(this))
+ {
+ Toast.makeText(this, R.string.attachment_encryption_unsupported, Toast.LENGTH_LONG).show();
+ }
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType(mime_type);
@@ -1050,6 +1294,14 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
+ // if a CryptoSystem activity is returning, then mPreventDraftSaving was set to true
+ mPreventDraftSaving = false;
+
+ if (mCrypto.onActivityResult(this, requestCode, resultCode, data))
+ {
+ return;
+ }
+
if (resultCode != RESULT_OK)
return;
if (data == null)
@@ -1059,7 +1311,6 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
switch (requestCode)
{
case ACTIVITY_REQUEST_PICK_ATTACHMENT:
-
addAttachment(data.getData());
mDraftNeedsSaving = true;
break;
@@ -1196,6 +1447,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
switch (item.getItemId())
{
case R.id.send:
+ mCrypto.setEncryptionKeys(null);
onSend();
break;
case R.id.save:
@@ -1442,7 +1694,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
Part part = MimeUtility.findFirstPartByMimeType(mSourceMessage,
"text/plain");
- if (part != null)
+ if (part != null || mSourceMessageBody != null)
{
String quotedText = String.format(
getString(R.string.message_compose_reply_header_fmt),
@@ -1452,8 +1704,16 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
// "$" and "\" in the quote prefix have to be escaped for
// the replaceAll() invocation.
final String escapedPrefix = prefix.replaceAll("(\\\\|\\$)", "\\\\$1");
- quotedText += MimeUtility.getTextFromPart(part).replaceAll(
- "(?m)^", escapedPrefix);
+
+ if (mSourceMessageBody != null)
+ {
+ quotedText += mSourceMessageBody.replaceAll("(?m)^", escapedPrefix);
+ }
+ else
+ {
+ quotedText += MimeUtility.getTextFromPart(part).replaceAll(
+ "(?m)^", escapedPrefix);
+ }
quotedText = quotedText.replaceAll("\\\r", "");
mQuotedText.setText(quotedText);
@@ -1548,9 +1808,13 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
{
part = MimeUtility.findFirstPartByMimeType(message, "text/html");
}
- if (part != null)
+ if (part != null || mSourceMessageBody != null)
{
- String quotedText = MimeUtility.getTextFromPart(part);
+ String quotedText = mSourceMessageBody;
+ if (quotedText == null)
+ {
+ quotedText = MimeUtility.getTextFromPart(part);
+ }
if (quotedText != null)
{
String text = String.format(
diff --git a/src/com/fsck/k9/activity/MessageList.java b/src/com/fsck/k9/activity/MessageList.java
index 61c85fc1a..c3f4299ed 100644
--- a/src/com/fsck/k9/activity/MessageList.java
+++ b/src/com/fsck/k9/activity/MessageList.java
@@ -1103,17 +1103,17 @@ public class MessageList
private void onReply(MessageInfoHolder holder)
{
- MessageCompose.actionReply(this, holder.message.getFolder().getAccount(), holder.message, false);
+ MessageCompose.actionReply(this, holder.message.getFolder().getAccount(), holder.message, false, null);
}
private void onReplyAll(MessageInfoHolder holder)
{
- MessageCompose.actionReply(this, holder.message.getFolder().getAccount(), holder.message, true);
+ MessageCompose.actionReply(this, holder.message.getFolder().getAccount(), holder.message, true, null);
}
private void onForward(MessageInfoHolder holder)
{
- MessageCompose.actionForward(this, holder.message.getFolder().getAccount(), holder.message);
+ MessageCompose.actionForward(this, holder.message.getFolder().getAccount(), holder.message, null);
}
private void onMarkAllAsRead(final Account account, final String folder)
diff --git a/src/com/fsck/k9/activity/MessageView.java b/src/com/fsck/k9/activity/MessageView.java
index 8947963cc..bfee03569 100644
--- a/src/com/fsck/k9/activity/MessageView.java
+++ b/src/com/fsck/k9/activity/MessageView.java
@@ -64,6 +64,7 @@ import com.fsck.k9.Preferences;
import com.fsck.k9.R;
import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.controller.MessagingListener;
+import com.fsck.k9.crypto.CryptoProvider;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Message;
@@ -83,6 +84,8 @@ public class MessageView extends K9Activity implements OnClickListener
private static final String EXTRA_MESSAGE_REFERENCES = "com.fsck.k9.MessageView_messageReferences";
private static final String EXTRA_NEXT = "com.fsck.k9.MessageView_next";
+ private static final String STATE_CRYPTO = "crypto";
+
private static final int ACTIVITY_CHOOSE_FOLDER_MOVE = 1;
private static final int ACTIVITY_CHOOSE_FOLDER_COPY = 2;
@@ -95,6 +98,12 @@ public class MessageView extends K9Activity implements OnClickListener
public View chip;
private CheckBox mFlagged;
private int defaultSubjectColor;
+ private View mDecryptLayout;
+ private Button mDecryptButton;
+ private LinearLayout mCryptoSignatureLayout = null;
+ private ImageView mCryptoSignatureStatusImage = null;
+ private TextView mCryptoSignatureUserId = null;
+ private TextView mCryptoSignatureUserIdRest = null;
private WebView mMessageContentView;
private LinearLayout mHeaderContainer;
private LinearLayout mAttachments;
@@ -125,17 +134,16 @@ public class MessageView extends K9Activity implements OnClickListener
private ArrayList mMessageReferences;
private Message mMessage;
+ private CryptoProvider mCrypto = null;
private static final int PREVIOUS = 1;
private static final int NEXT = 2;
private int mLastDirection = PREVIOUS;
-
private MessageReference mNextMessage = null;
private MessageReference mPreviousMessage = null;
-
private Menu optionsMenu = null;
private Listener mListener = new Listener();
@@ -734,6 +742,39 @@ public class MessageView extends K9Activity implements OnClickListener
mTopView = mToggleScrollView = (ToggleScrollView)findViewById(R.id.top_view);
mMessageContentView = (WebView)findViewById(R.id.message_content);
+ mDecryptLayout = (View)findViewById(R.id.layout_decrypt);
+ mDecryptButton = (Button)findViewById(R.id.btn_decrypt);
+ mDecryptButton.setOnClickListener(new OnClickListener()
+ {
+ @Override
+ public void onClick(View v)
+ {
+ try
+ {
+ String data = null;
+ Part part = MimeUtility.findFirstPartByMimeType(mMessage, "text/plain");
+ if (part == null)
+ {
+ part = MimeUtility.findFirstPartByMimeType(mMessage, "text/html");
+ }
+ if (part != null)
+ {
+ data = MimeUtility.getTextFromPart(part);
+ }
+ mCrypto.decrypt(MessageView.this, data);
+ }
+ catch (MessagingException me)
+ {
+ Log.e(K9.LOG_TAG, "Unable to decrypt email.", me);
+ }
+ }
+ });
+ mCryptoSignatureLayout = (LinearLayout) findViewById(R.id.crypto_signature);
+ mCryptoSignatureStatusImage = (ImageView) findViewById(R.id.ic_crypto_signature_status);
+ mCryptoSignatureUserId = (TextView) findViewById(R.id.userId);
+ mCryptoSignatureUserIdRest = (TextView) findViewById(R.id.userIdRest);
+ mCryptoSignatureLayout.setVisibility(View.INVISIBLE);
+
mAttachments = (LinearLayout)findViewById(R.id.attachments);
mAttachmentIcon = findViewById(R.id.attachment);
mShowPicturesSection = findViewById(R.id.show_pictures_section);
@@ -808,14 +849,17 @@ public class MessageView extends K9Activity implements OnClickListener
Intent intent = getIntent();
Uri uri = intent.getData();
- if (icicle!=null)
+ if (icicle != null)
{
mMessageReference = (MessageReference)icicle.getSerializable(EXTRA_MESSAGE_REFERENCE);
mMessageReferences = (ArrayList)icicle.getSerializable(EXTRA_MESSAGE_REFERENCES);
+
+ mCrypto = (CryptoProvider) icicle.getSerializable(STATE_CRYPTO);
+ updateDecryptLayout();
}
else
{
- if (uri==null)
+ if (uri == null)
{
mMessageReference = (MessageReference)intent.getSerializableExtra(EXTRA_MESSAGE_REFERENCE);
mMessageReferences = (ArrayList)intent.getSerializableExtra(EXTRA_MESSAGE_REFERENCES);
@@ -823,7 +867,7 @@ public class MessageView extends K9Activity implements OnClickListener
else
{
List segmentList = uri.getPathSegments();
- if (segmentList.size()==3)
+ if (segmentList.size() == 3)
{
String accountId = segmentList.get(0);
Account[] accounts = Preferences.getPreferences(this).getAccounts();
@@ -859,6 +903,7 @@ public class MessageView extends K9Activity implements OnClickListener
}
}
}
+
if (K9.DEBUG)
Log.d(K9.LOG_TAG, "MessageView got message " + mMessageReference);
@@ -953,6 +998,18 @@ public class MessageView extends K9Activity implements OnClickListener
{
outState.putSerializable(EXTRA_MESSAGE_REFERENCE, mMessageReference);
outState.putSerializable(EXTRA_MESSAGE_REFERENCES, mMessageReferences);
+ outState.putSerializable(STATE_CRYPTO, mCrypto);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Bundle savedInstanceState)
+ {
+ super.onRestoreInstanceState(savedInstanceState);
+
+ mCrypto = (CryptoProvider) savedInstanceState.getSerializable(STATE_CRYPTO);
+ initializeCrypto();
+
+ updateDecryptLayout();
}
private void displayMessage(MessageReference ref)
@@ -970,6 +1027,9 @@ public class MessageView extends K9Activity implements OnClickListener
mAttachments.removeAllViews();
findSurroundingMessagesUid();
+ mCrypto = null;
+ initializeCrypto();
+
setupDisplayMessageButtons();
MessagingController.getInstance(getApplication()).loadMessageForView(
@@ -1251,7 +1311,7 @@ public class MessageView extends K9Activity implements OnClickListener
{
if (mMessage != null)
{
- MessageCompose.actionReply(this, mAccount, mMessage, false);
+ MessageCompose.actionReply(this, mAccount, mMessage, false, mCrypto.getDecryptedData());
finish();
}
}
@@ -1260,7 +1320,7 @@ public class MessageView extends K9Activity implements OnClickListener
{
if (mMessage != null)
{
- MessageCompose.actionReply(this, mAccount, mMessage, true);
+ MessageCompose.actionReply(this, mAccount, mMessage, true, mCrypto.getDecryptedData());
finish();
}
}
@@ -1269,7 +1329,7 @@ public class MessageView extends K9Activity implements OnClickListener
{
if (mMessage != null)
{
- MessageCompose.actionForward(this, mAccount, mMessage);
+ MessageCompose.actionForward(this, mAccount, mMessage, mCrypto.getDecryptedData());
finish();
}
}
@@ -1376,6 +1436,11 @@ public class MessageView extends K9Activity implements OnClickListener
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
+ if (mCrypto.onActivityResult(this, requestCode, resultCode, data))
+ {
+ return;
+ }
+
if (resultCode != RESULT_OK)
return;
@@ -1409,6 +1474,9 @@ public class MessageView extends K9Activity implements OnClickListener
break;
}
}
+ break;
+
+
}
}
@@ -1966,6 +2034,7 @@ public class MessageView extends K9Activity implements OnClickListener
public void run()
{
mMessageContentView.loadUrl("file:///android_asset/downloading.html");
+ updateDecryptLayout();
}
});
}
@@ -2007,30 +2076,39 @@ public class MessageView extends K9Activity implements OnClickListener
mHandler.removeAllAttachments();
String text;
- Part part = MimeUtility.findFirstPartByMimeType(mMessage, "text/html");
- if (part == null)
+ String type = "text/html";
+ if (mCrypto.getDecryptedData() != null)
{
- part = MimeUtility.findFirstPartByMimeType(mMessage, "text/plain");
+ text = mCrypto.getDecryptedData();
+ type = "text/plain";
+ }
+ else
+ {
+ Part part = MimeUtility.findFirstPartByMimeType(mMessage, "text/html");
if (part == null)
{
- text = null;
- }
- else
- {
- LocalTextBody body = (LocalTextBody)part.getBody();
- if (body == null)
+ part = MimeUtility.findFirstPartByMimeType(mMessage, "text/plain");
+ if (part == null)
{
text = null;
}
else
{
- text = body.getBodyForDisplay();
+ LocalTextBody body = (LocalTextBody)part.getBody();
+ if (body == null)
+ {
+ text = null;
+ }
+ else
+ {
+ text = body.getBodyForDisplay();
+ }
}
}
- }
- else
- {
- text = MimeUtility.getTextFromPart(part);
+ else
+ {
+ text = MimeUtility.getTextFromPart(part);
+ }
}
if (text != null)
@@ -2040,11 +2118,13 @@ public class MessageView extends K9Activity implements OnClickListener
* get background images and a million other things that HTML allows.
*/
final String emailText = text;
+ final String mimeType = type;
mHandler.post(new Runnable()
{
public void run()
{
- mMessageContentView.loadDataWithBaseURL("http://", emailText, "text/html", "utf-8", null);
+ mMessageContentView.loadDataWithBaseURL("http://", emailText, mimeType, "utf-8", null);
+ updateDecryptLayout();
}
});
mHandler.showShowPictures(text.contains(" 1)
+ {
+ mCryptoSignatureUserIdRest.setText("<" + chunks[1]);
+ }
+ mCryptoSignatureUserId.setText(name);
+
+ if (mCrypto.getSignatureSuccess())
+ {
+ mCryptoSignatureStatusImage.setImageResource(R.drawable.overlay_ok);
+ }
+ else if (mCrypto.getSignatureUnknown())
+ {
+ mCryptoSignatureStatusImage.setImageResource(R.drawable.overlay_error);
+ }
+ else
+ {
+ mCryptoSignatureStatusImage.setImageResource(R.drawable.overlay_error);
+ }
+ mCryptoSignatureLayout.setVisibility(View.VISIBLE);
+ mDecryptLayout.setVisibility(View.VISIBLE);
+ }
+
+ if (!true || ((mMessage == null) && (mCrypto.getDecryptedData() == null)))
+ {
+ mDecryptLayout.setVisibility(View.GONE);
+ return;
+ }
+
+ if (mCrypto.getDecryptedData() != null)
+ {
+ if (mCrypto.getSignatureKeyId() == 0)
+ {
+ mDecryptLayout.setVisibility(View.GONE);
+ }
+ else
+ {
+ // no need to show this after decryption/verification
+ mDecryptButton.setVisibility(View.GONE);
+ }
+ return;
+ }
+
+ mDecryptButton.setVisibility(View.VISIBLE);
+
+ if (mCrypto.isEncrypted(mMessage))
+ {
+ mDecryptButton.setText(R.string.btn_decrypt);
+ mDecryptLayout.setVisibility(View.VISIBLE);
+ }
+ else if (mCrypto.isSigned(mMessage))
+ {
+ mDecryptButton.setText(R.string.btn_verify);
+ mDecryptLayout.setVisibility(View.VISIBLE);
+ }
+ else
+ {
+ mDecryptLayout.setVisibility(View.GONE);
+ try
+ {
+ // check for PGP/MIME encryption
+ Part pgp = MimeUtility.findFirstPartByMimeType(mMessage, "application/pgp-encrypted");
+ if (pgp != null)
+ {
+ Toast.makeText(this, R.string.pgp_mime_unsupported, Toast.LENGTH_LONG).show();
+ }
+ }
+ catch (MessagingException e)
+ {
+ // nothing to do...
+ }
+ }
+ }
+
+ public void onDecryptDone()
+ {
+ // TODO: this might not be enough if the orientation was changed while in APG,
+ // sometimes shows the original encrypted content
+ mMessageContentView.loadDataWithBaseURL("email://", mCrypto.getDecryptedData(), "text/plain", "utf-8", null);
+ updateDecryptLayout();
+ }
+
+ /*
* Emulate the shift key being pressed to trigger the text selection mode
* of a WebView.
*/
diff --git a/src/com/fsck/k9/activity/setup/AccountSettings.java b/src/com/fsck/k9/activity/setup/AccountSettings.java
index cb7ea1b6c..dbd7530e5 100644
--- a/src/com/fsck/k9/activity/setup/AccountSettings.java
+++ b/src/com/fsck/k9/activity/setup/AccountSettings.java
@@ -5,16 +5,25 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
-import android.preference.*;
+import android.preference.CheckBoxPreference;
+import android.preference.EditTextPreference;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.RingtonePreference;
import android.util.Log;
import android.view.KeyEvent;
-import com.fsck.k9.*;
+
+import com.fsck.k9.Account;
import com.fsck.k9.Account.FolderMode;
+import com.fsck.k9.K9;
+import com.fsck.k9.Preferences;
+import com.fsck.k9.R;
import com.fsck.k9.activity.ChooseFolder;
import com.fsck.k9.activity.ChooseIdentity;
import com.fsck.k9.activity.ColorPickerDialog;
import com.fsck.k9.activity.K9PreferenceActivity;
import com.fsck.k9.activity.ManageIdentities;
+import com.fsck.k9.crypto.Apg;
import com.fsck.k9.mail.Store;
import com.fsck.k9.service.MailService;
@@ -60,7 +69,8 @@ public class AccountSettings extends K9PreferenceActivity
private static final String PREFERENCE_MESSAGE_SIZE = "account_autodownload_size";
private static final String PREFERENCE_QUOTE_PREFIX = "account_quote_prefix";
private static final String PREFERENCE_SYNC_REMOTE_DELETIONS = "account_sync_remote_deletetions";
-
+ private static final String PREFERENCE_CRYPTO_APP = "crypto_app";
+ private static final String PREFERENCE_CRYPTO_AUTO_SIGNATURE = "crypto_auto_signature";
private Account mAccount;
@@ -94,7 +104,8 @@ public class AccountSettings extends K9PreferenceActivity
private CheckBoxPreference mNotificationOpensUnread;
private EditTextPreference mAccountQuotePrefix;
private CheckBoxPreference mSyncRemoteDeletions;
-
+ private ListPreference mCryptoApp;
+ private CheckBoxPreference mCryptoAutoSignature;
public static void actionSettings(Context context, Account account)
{
@@ -510,6 +521,53 @@ public class AccountSettings extends K9PreferenceActivity
return true;
}
});
+
+ mCryptoApp = (ListPreference) findPreference(PREFERENCE_CRYPTO_APP);
+ CharSequence cryptoAppEntries[] = mCryptoApp.getEntries();
+ if (!new Apg().isAvailable(this))
+ {
+ int apgIndex = mCryptoApp.findIndexOfValue(Apg.NAME);
+ if (apgIndex >= 0)
+ {
+ cryptoAppEntries[apgIndex] = "APG (" + getResources().getString(R.string.account_settings_crypto_app_not_available) + ")";
+ mCryptoApp.setEntries(cryptoAppEntries);
+ }
+ }
+ mCryptoApp.setValue(String.valueOf(mAccount.getCryptoApp()));
+ mCryptoApp.setSummary(mCryptoApp.getEntry());
+ mCryptoApp.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener()
+ {
+ public boolean onPreferenceChange(Preference preference, Object newValue)
+ {
+ String value = newValue.toString();
+ int index = mCryptoApp.findIndexOfValue(value);
+ mCryptoApp.setSummary(mCryptoApp.getEntries()[index]);
+ mCryptoApp.setValue(value);
+ handleCryptoAppDependencies();
+ if (Apg.NAME.equals(value))
+ {
+ Apg.createInstance(null).test(AccountSettings.this);
+ }
+ return false;
+ }
+ });
+
+ mCryptoAutoSignature = (CheckBoxPreference) findPreference(PREFERENCE_CRYPTO_AUTO_SIGNATURE);
+ mCryptoAutoSignature.setChecked(mAccount.getCryptoAutoSignature());
+
+ handleCryptoAppDependencies();
+ }
+
+ private void handleCryptoAppDependencies()
+ {
+ if ("".equals(mCryptoApp.getValue()))
+ {
+ mCryptoAutoSignature.setEnabled(false);
+ }
+ else
+ {
+ mCryptoAutoSignature.setEnabled(true);
+ }
}
@Override
@@ -543,6 +601,8 @@ public class AccountSettings extends K9PreferenceActivity
mAccount.setSyncRemoteDeletions(mSyncRemoteDeletions.isChecked());
mAccount.setSearchableFolders(Account.Searchable.valueOf(mSearchableFolders.getValue()));
mAccount.setQuotePrefix(mAccountQuotePrefix.getText());
+ mAccount.setCryptoApp(mCryptoApp.getValue());
+ mAccount.setCryptoAutoSignature(mCryptoAutoSignature.isChecked());
boolean needsRefresh = mAccount.setAutomaticCheckIntervalMinutes(Integer.parseInt(mCheckFrequency.getValue()));
needsRefresh |= mAccount.setFolderSyncMode(Account.FolderMode.valueOf(mSyncMode.getValue()));
diff --git a/src/com/fsck/k9/crypto/Apg.java b/src/com/fsck/k9/crypto/Apg.java
new file mode 100644
index 000000000..c1e4df804
--- /dev/null
+++ b/src/com/fsck/k9/crypto/Apg.java
@@ -0,0 +1,556 @@
+package com.fsck.k9.crypto;
+
+import java.util.Vector;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import android.app.Activity;
+import android.content.ActivityNotFoundException;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.database.Cursor;
+import android.net.Uri;
+import android.widget.Toast;
+
+import com.fsck.k9.Account;
+import com.fsck.k9.R;
+import com.fsck.k9.activity.MessageCompose;
+import com.fsck.k9.activity.MessageView;
+import com.fsck.k9.mail.Message;
+import com.fsck.k9.mail.MessagingException;
+import com.fsck.k9.mail.Part;
+import com.fsck.k9.mail.internet.MimeUtility;
+
+public class Apg extends CryptoProvider
+{
+ static final long serialVersionUID = 0x21071235;
+ public static final String NAME = "apg";
+
+ private static final String mApgPackageName = "org.thialfihar.android.apg";
+ private static final int mMinRequiredVersion = 16;
+
+ public static final String AUTHORITY = "org.thialfihar.android.apg.provider";
+ public static final Uri CONTENT_URI_SECRET_KEY_RING_BY_KEY_ID =
+ Uri.parse("content://" + AUTHORITY + "/key_rings/secret/key_id/");
+ public static final Uri CONTENT_URI_SECRET_KEY_RING_BY_EMAILS =
+ Uri.parse("content://" + AUTHORITY + "/key_rings/secret/emails/");
+
+ public static final Uri CONTENT_URI_PUBLIC_KEY_RING_BY_KEY_ID =
+ Uri.parse("content://" + AUTHORITY + "/key_rings/public/key_id/");
+ public static final Uri CONTENT_URI_PUBLIC_KEY_RING_BY_EMAILS =
+ Uri.parse("content://" + AUTHORITY + "/key_rings/public/emails/");
+
+ public static class Intent
+ {
+ public static final String DECRYPT = "org.thialfihar.android.apg.intent.DECRYPT";
+ public static final String ENCRYPT = "org.thialfihar.android.apg.intent.ENCRYPT";
+ public static final String DECRYPT_FILE = "org.thialfihar.android.apg.intent.DECRYPT_FILE";
+ public static final String ENCRYPT_FILE = "org.thialfihar.android.apg.intent.ENCRYPT_FILE";
+ public static final String DECRYPT_AND_RETURN = "org.thialfihar.android.apg.intent.DECRYPT_AND_RETURN";
+ public static final String ENCRYPT_AND_RETURN = "org.thialfihar.android.apg.intent.ENCRYPT_AND_RETURN";
+ public static final String SELECT_PUBLIC_KEYS = "org.thialfihar.android.apg.intent.SELECT_PUBLIC_KEYS";
+ public static final String SELECT_SECRET_KEY = "org.thialfihar.android.apg.intent.SELECT_SECRET_KEY";
+ }
+
+ public static final String EXTRA_TEXT = "text";
+ public static final String EXTRA_DATA = "data";
+ public static final String EXTRA_STATUS = "status";
+ public static final String EXTRA_ERROR = "error";
+ public static final String EXTRA_DECRYPTED_MESSAGE = "decryptedMessage";
+ public static final String EXTRA_ENCRYPTED_MESSAGE = "encryptedMessage";
+ public static final String EXTRA_SIGNATURE = "signature";
+ public static final String EXTRA_SIGNATURE_KEY_ID = "signatureKeyId";
+ public static final String EXTRA_SIGNATURE_USER_ID = "signatureUserId";
+ public static final String EXTRA_SIGNATURE_SUCCESS = "signatureSuccess";
+ public static final String EXTRA_SIGNATURE_UNKNOWN = "signatureUnknown";
+ public static final String EXTRA_USER_ID = "userId";
+ public static final String EXTRA_KEY_ID = "keyId";
+ public static final String EXTRA_REPLY_TO = "replyTo";
+ public static final String EXTRA_SEND_TO = "sendTo";
+ public static final String EXTRA_SUBJECT = "subject";
+ public static final String EXTRA_ENCRYPTION_KEY_IDS = "encryptionKeyIds";
+ public static final String EXTRA_SELECTION = "selection";
+ public static final String EXTRA_MESSAGE = "message";
+ public static final String EXTRA_PROGRESS = "progress";
+ public static final String EXTRA_MAX = "max";
+ public static final String EXTRA_ACCOUNT = "account";
+
+ public static final int DECRYPT_MESSAGE = 0x21070001;
+ public static final int ENCRYPT_MESSAGE = 0x21070002;
+ public static final int SELECT_PUBLIC_KEYS = 0x21070003;
+ public static final int SELECT_SECRET_KEY = 0x21070004;
+
+ public static Pattern PGP_MESSAGE =
+ Pattern.compile(".*?(-----BEGIN PGP MESSAGE-----.*?-----END PGP MESSAGE-----).*",
+ Pattern.DOTALL);
+
+ public static Pattern PGP_SIGNED_MESSAGE =
+ Pattern.compile(".*?(-----BEGIN PGP SIGNED MESSAGE-----.*?-----BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----).*",
+ Pattern.DOTALL);
+
+ public static Apg createInstance(Account account)
+ {
+ return new Apg();
+ }
+
+ /**
+ * Check whether APG is installed and at a high enough version.
+ *
+ * @param context
+ * @return whether a suitable version of APG was found
+ */
+ @Override
+ public boolean isAvailable(Context context)
+ {
+ try
+ {
+ PackageInfo pi = context.getPackageManager().getPackageInfo(mApgPackageName, 0);
+ if (pi.versionCode >= mMinRequiredVersion)
+ {
+ return true;
+ }
+ else
+ {
+ Toast.makeText(context,
+ R.string.error_apg_version_not_supported, Toast.LENGTH_SHORT).show();
+ }
+ }
+ catch (NameNotFoundException e)
+ {
+ // not found
+ }
+
+ return false;
+ }
+
+ /**
+ * Select the signature key.
+ *
+ * @param activity
+ * @return success or failure
+ */
+ @Override
+ public boolean selectSecretKey(Activity activity)
+ {
+ android.content.Intent intent = new android.content.Intent(Intent.SELECT_SECRET_KEY);
+ try
+ {
+ activity.startActivityForResult(intent, Apg.SELECT_SECRET_KEY);
+ return true;
+ }
+ catch (ActivityNotFoundException e)
+ {
+ Toast.makeText(activity,
+ R.string.error_activity_not_found,
+ Toast.LENGTH_SHORT).show();
+ return false;
+ }
+ }
+
+ /**
+ * Select encryption keys.
+ *
+ * @param activity
+ * @param emails The emails that should be used for preselection.
+ * @return success or failure
+ */
+ @Override
+ public boolean selectEncryptionKeys(Activity activity, String emails)
+ {
+ android.content.Intent intent = new android.content.Intent(Apg.Intent.SELECT_PUBLIC_KEYS);
+ long[] initialKeyIds = null;
+ if (!hasEncryptionKeys())
+ {
+ Vector keyIds = new Vector();
+ if (hasSignatureKey())
+ {
+ keyIds.add(getSignatureKeyId());
+ }
+
+ try
+ {
+ Uri contentUri = Uri.withAppendedPath(
+ Apg.CONTENT_URI_PUBLIC_KEY_RING_BY_EMAILS,
+ emails);
+ Cursor c = activity.getContentResolver().query(contentUri,
+ new String[] { "master_key_id" },
+ null, null, null);
+ if (c != null)
+ {
+ while (c.moveToNext())
+ {
+ keyIds.add(c.getLong(0));
+ }
+ }
+
+ if (c != null)
+ {
+ c.close();
+ }
+ }
+ catch (SecurityException e)
+ {
+ Toast.makeText(activity,
+ activity.getResources().getString(R.string.insufficient_apg_permissions),
+ Toast.LENGTH_LONG).show();
+ }
+ if (keyIds.size() > 0)
+ {
+ initialKeyIds = new long[keyIds.size()];
+ for (int i = 0, size = keyIds.size(); i < size; ++i)
+ {
+ initialKeyIds[i] = keyIds.get(i);
+ }
+ }
+ }
+ else
+ {
+ initialKeyIds = mEncryptionKeyIds;
+ }
+ intent.putExtra(Apg.EXTRA_SELECTION, initialKeyIds);
+ try
+ {
+ activity.startActivityForResult(intent, Apg.SELECT_PUBLIC_KEYS);
+ return true;
+ }
+ catch (ActivityNotFoundException e)
+ {
+ Toast.makeText(activity,
+ R.string.error_activity_not_found,
+ Toast.LENGTH_SHORT).show();
+ return false;
+ }
+ }
+
+ /**
+ * Get secret key ids based on a given email.
+ *
+ * @param context
+ * @param email The email in question.
+ * @return key ids
+ */
+ @Override
+ public long[] getSecretKeyIdsFromEmail(Context context, String email)
+ {
+ long ids[] = null;
+ 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)
+ {
+ ids = new long[c.getCount()];
+ while (c.moveToNext())
+ {
+ ids[c.getPosition()] = c.getLong(0);
+ }
+ }
+
+ if (c != null)
+ {
+ c.close();
+ }
+ }
+ catch (SecurityException e)
+ {
+ Toast.makeText(context,
+ context.getResources().getString(R.string.insufficient_apg_permissions),
+ Toast.LENGTH_LONG).show();
+ }
+
+ return ids;
+ }
+
+ /**
+ * Get the user id based on the key id.
+ *
+ * @param context
+ * @param keyId
+ * @return user id
+ */
+ @Override
+ public String getUserId(Context context, long keyId)
+ {
+ String userId = null;
+ try
+ {
+ Uri contentUri = ContentUris.withAppendedId(
+ Apg.CONTENT_URI_SECRET_KEY_RING_BY_KEY_ID,
+ keyId);
+ Cursor c = context.getContentResolver().query(contentUri,
+ new String[] { "user_id" },
+ null, null, null);
+ if (c != null && c.moveToFirst())
+ {
+ userId = c.getString(0);
+ }
+
+ if (c != null)
+ {
+ c.close();
+ }
+ }
+ catch (SecurityException e)
+ {
+ Toast.makeText(context,
+ context.getResources().getString(R.string.insufficient_apg_permissions),
+ Toast.LENGTH_LONG).show();
+ }
+
+ if (userId == null)
+ {
+ userId = context.getString(R.string.unknown_crypto_signature_user_id);
+ }
+ return userId;
+ }
+
+ /**
+ * Handle the activity results that concern us.
+ *
+ * @param activity
+ * @param requestCode
+ * @param resultCode
+ * @param data
+ * @return handled or not
+ */
+ @Override
+ public boolean onActivityResult(Activity activity, int requestCode, int resultCode,
+ android.content.Intent data)
+ {
+ switch (requestCode)
+ {
+ case Apg.SELECT_SECRET_KEY:
+ if (resultCode != Activity.RESULT_OK || data == null)
+ {
+ break;
+ }
+ setSignatureKeyId(data.getLongExtra(Apg.EXTRA_KEY_ID, 0));
+ setSignatureUserId(data.getStringExtra(Apg.EXTRA_USER_ID));
+ ((MessageCompose) activity).updateEncryptLayout();
+ break;
+
+ case Apg.SELECT_PUBLIC_KEYS:
+ if (resultCode != Activity.RESULT_OK || data == null)
+ {
+ mEncryptionKeyIds = null;
+ ((MessageCompose) activity).onEncryptionKeySelectionDone();
+ break;
+ }
+ mEncryptionKeyIds = data.getLongArrayExtra(Apg.EXTRA_SELECTION);
+ ((MessageCompose) activity).onEncryptionKeySelectionDone();
+ break;
+
+ case Apg.ENCRYPT_MESSAGE:
+ if (resultCode != Activity.RESULT_OK || data == null)
+ {
+ mEncryptedData = null;
+ ((MessageCompose) activity).onEncryptDone();
+ break;
+ }
+ mEncryptedData = data.getStringExtra(Apg.EXTRA_ENCRYPTED_MESSAGE);
+ // this was a stupid bug in an earlier version, just gonna leave this in for an APG
+ // version or two
+ if (mEncryptedData == null) {
+ mEncryptedData = data.getStringExtra(Apg.EXTRA_DECRYPTED_MESSAGE);
+ }
+ if (mEncryptedData != null)
+ {
+ ((MessageCompose) activity).onEncryptDone();
+ }
+ break;
+
+ case Apg.DECRYPT_MESSAGE:
+ if (resultCode != Activity.RESULT_OK || data == null)
+ {
+ break;
+ }
+
+ mSignatureUserId = data.getStringExtra(Apg.EXTRA_SIGNATURE_USER_ID);
+ mSignatureKeyId = data.getLongExtra(Apg.EXTRA_SIGNATURE_KEY_ID, 0);
+ mSignatureSuccess = data.getBooleanExtra(Apg.EXTRA_SIGNATURE_SUCCESS, false);
+ mSignatureUnknown = data.getBooleanExtra(Apg.EXTRA_SIGNATURE_UNKNOWN, false);
+
+ mDecryptedData = data.getStringExtra(Apg.EXTRA_DECRYPTED_MESSAGE);
+ ((MessageView) activity).onDecryptDone();
+
+ break;
+
+ default:
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Start the encrypt activity.
+ *
+ * @param activity
+ * @param data
+ * @return success or failure
+ */
+ @Override
+ public boolean encrypt(Activity activity, String data)
+ {
+ android.content.Intent intent = new android.content.Intent(Intent.ENCRYPT_AND_RETURN);
+ intent.setType("text/plain");
+ intent.putExtra(Apg.EXTRA_TEXT, data);
+ intent.putExtra(Apg.EXTRA_ENCRYPTION_KEY_IDS, mEncryptionKeyIds);
+ intent.putExtra(Apg.EXTRA_SIGNATURE_KEY_ID, mSignatureKeyId);
+ try
+ {
+ activity.startActivityForResult(intent, Apg.ENCRYPT_MESSAGE);
+ return true;
+ }
+ catch (ActivityNotFoundException e)
+ {
+ Toast.makeText(activity,
+ R.string.error_activity_not_found,
+ Toast.LENGTH_SHORT).show();
+ return false;
+ }
+ }
+
+ /**
+ * Start the decrypt activity.
+ *
+ * @param activity
+ * @param data
+ * @return success or failure
+ */
+ @Override
+ public boolean decrypt(Activity activity, String data)
+ {
+ android.content.Intent intent = new android.content.Intent(Apg.Intent.DECRYPT_AND_RETURN);
+ intent.setType("text/plain");
+ if (data == null)
+ {
+ return false;
+ }
+ try
+ {
+ intent.putExtra(EXTRA_TEXT, data);
+ activity.startActivityForResult(intent, Apg.DECRYPT_MESSAGE);
+ return true;
+ }
+ catch (ActivityNotFoundException e)
+ {
+ Toast.makeText(activity,
+ R.string.error_activity_not_found,
+ Toast.LENGTH_SHORT).show();
+ return false;
+ }
+ }
+
+ public boolean isEncrypted(Message message)
+ {
+ String data = null;
+ try {
+ Part part = MimeUtility.findFirstPartByMimeType(message, "text/plain");
+ if (part == null)
+ {
+ part = MimeUtility.findFirstPartByMimeType(message, "text/html");
+ }
+ if (part != null)
+ {
+ data = MimeUtility.getTextFromPart(part);
+ }
+ }
+ catch (MessagingException e)
+ {
+ // guess not...
+ // TODO: maybe log this?
+ }
+
+ if (data == null)
+ {
+ return false;
+ }
+
+ Matcher matcher = PGP_MESSAGE.matcher(data);
+ return matcher.matches();
+ }
+
+ public boolean isSigned(Message message)
+ {
+ String data = null;
+ try {
+ Part part = MimeUtility.findFirstPartByMimeType(message, "text/plain");
+ if (part == null)
+ {
+ part = MimeUtility.findFirstPartByMimeType(message, "text/html");
+ }
+ if (part != null)
+ {
+ data = MimeUtility.getTextFromPart(part);
+ }
+ }
+ catch (MessagingException e)
+ {
+ // guess not...
+ // TODO: maybe log this?
+ }
+
+ if (data == null)
+ {
+ return false;
+ }
+
+ Matcher matcher = PGP_SIGNED_MESSAGE.matcher(data);
+ return matcher.matches();
+ }
+
+ /**
+ * Get the name of the provider.
+ *
+ * @return provider name
+ */
+ @Override
+ public String getName()
+ {
+ return NAME;
+ }
+
+ /**
+ * Test the APG installation.
+ *
+ * @return success or failure
+ */
+ public boolean test(Context context)
+ {
+ if (!isAvailable(context))
+ {
+ return false;
+ }
+
+ try
+ {
+ // try out one content provider to check permissions
+ Uri contentUri = ContentUris.withAppendedId(
+ Apg.CONTENT_URI_SECRET_KEY_RING_BY_KEY_ID,
+ 12345);
+ Cursor c = context.getContentResolver().query(contentUri,
+ new String[] { "user_id" },
+ null, null, null);
+ if (c != null)
+ {
+ c.close();
+ }
+ }
+ catch (SecurityException e)
+ {
+ // if there was a problem, then let the user know, this will not stop K9/APG from
+ // working, but some features won't be available, so we can still return "true"
+ Toast.makeText(context,
+ context.getResources().getString(R.string.insufficient_apg_permissions),
+ Toast.LENGTH_LONG).show();
+ }
+
+ return true;
+ }
+}
diff --git a/src/com/fsck/k9/crypto/CryptoProvider.java b/src/com/fsck/k9/crypto/CryptoProvider.java
new file mode 100644
index 000000000..a172de54c
--- /dev/null
+++ b/src/com/fsck/k9/crypto/CryptoProvider.java
@@ -0,0 +1,107 @@
+package com.fsck.k9.crypto;
+
+import java.io.Serializable;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+
+import com.fsck.k9.Account;
+import com.fsck.k9.mail.Message;
+
+abstract public class CryptoProvider implements Serializable
+{
+ static final long serialVersionUID = 0x21071234;
+
+ protected long mEncryptionKeyIds[] = null;
+ protected long mSignatureKeyId = 0;
+ protected String mSignatureUserId = null;
+ protected boolean mSignatureSuccess = false;
+ protected boolean mSignatureUnknown = false;
+ protected String mDecryptedData = null;
+ protected String mEncryptedData = null;
+
+ abstract public boolean isAvailable(Context context);
+ abstract public boolean isEncrypted(Message message);
+ abstract public boolean isSigned(Message message);
+ abstract public boolean onActivityResult(Activity activity, int requestCode, int resultCode,
+ Intent data);
+ abstract public boolean selectSecretKey(Activity activity);
+ abstract public boolean selectEncryptionKeys(Activity activity, String emails);
+ abstract public boolean encrypt(Activity activity, String data);
+ abstract public boolean decrypt(Activity activity, String data);
+ abstract public long[] getSecretKeyIdsFromEmail(Context context, String email);
+ abstract public String getUserId(Context context, long keyId);
+ abstract public String getName();
+ abstract public boolean test(Context context);
+
+ public static CryptoProvider createInstance(Account account)
+ {
+ if (Apg.NAME.equals(account.getCryptoApp()))
+ {
+ return Apg.createInstance(account);
+ }
+
+ return None.createInstance(account);
+ }
+
+ public void setSignatureKeyId(long keyId)
+ {
+ mSignatureKeyId = keyId;
+ }
+
+ public long getSignatureKeyId()
+ {
+ return mSignatureKeyId;
+ }
+
+ public void setEncryptionKeys(long keyIds[])
+ {
+ mEncryptionKeyIds = keyIds;
+ }
+
+ public long[] getEncryptionKeys()
+ {
+ return mEncryptionKeyIds;
+ }
+
+ public boolean hasSignatureKey()
+ {
+ return mSignatureKeyId != 0;
+ }
+
+ public boolean hasEncryptionKeys()
+ {
+ return (mEncryptionKeyIds != null) && (mEncryptionKeyIds.length > 0);
+ }
+
+ public String getEncryptedData()
+ {
+ return mEncryptedData;
+ }
+
+ public String getDecryptedData()
+ {
+ return mDecryptedData;
+ }
+
+ public void setSignatureUserId(String userId)
+ {
+ mSignatureUserId = userId;
+ }
+
+ public String getSignatureUserId()
+ {
+ return mSignatureUserId;
+ }
+
+ public boolean getSignatureSuccess()
+ {
+ return mSignatureSuccess;
+ }
+
+ public boolean getSignatureUnknown()
+ {
+ return mSignatureUnknown;
+ }
+}
diff --git a/src/com/fsck/k9/crypto/None.java b/src/com/fsck/k9/crypto/None.java
new file mode 100644
index 000000000..e96240267
--- /dev/null
+++ b/src/com/fsck/k9/crypto/None.java
@@ -0,0 +1,89 @@
+package com.fsck.k9.crypto;
+
+import android.app.Activity;
+import android.content.Context;
+
+import com.fsck.k9.Account;
+import com.fsck.k9.mail.Message;
+
+public class None extends CryptoProvider
+{
+ static final long serialVersionUID = 0x21071230;
+ public static final String NAME = "";
+
+ public static None createInstance(Account account)
+ {
+ return new None();
+ }
+
+ @Override
+ public boolean isAvailable(Context context)
+ {
+ return false;
+ }
+
+ @Override
+ public boolean selectSecretKey(Activity activity)
+ {
+ return false;
+ }
+
+ @Override
+ public boolean selectEncryptionKeys(Activity activity, String emails)
+ {
+ return false;
+ }
+
+ @Override
+ public long[] getSecretKeyIdsFromEmail(Context context, String email)
+ {
+ return null;
+ }
+
+ @Override
+ public String getUserId(Context context, long keyId)
+ {
+ return null;
+ }
+
+ @Override
+ public boolean onActivityResult(Activity activity, int requestCode, int resultCode,
+ android.content.Intent data)
+ {
+ return false;
+ }
+
+ @Override
+ public boolean encrypt(Activity activity, String data)
+ {
+ return false;
+ }
+
+ @Override
+ public boolean decrypt(Activity activity, String data)
+ {
+ return false;
+ }
+
+ public boolean isEncrypted(Message message)
+ {
+ return false;
+ }
+
+ public boolean isSigned(Message message)
+ {
+ return false;
+ }
+
+ @Override
+ public String getName()
+ {
+ return NAME;
+ }
+
+ @Override
+ public boolean test(Context context)
+ {
+ return true;
+ }
+}