1
0
mirror of https://github.com/moparisthebest/k-9 synced 2024-11-27 11:42:16 -05:00

Merge from apg-integration

This commit is contained in:
Jesse Vincent 2010-07-27 12:10:09 +00:00
parent bae8a9736d
commit 7a4d12b53b
18 changed files with 1553 additions and 84 deletions

View File

@ -22,6 +22,8 @@
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="org.thialfihar.android.apg.permission.READ_KEY_DETAILS" />
<permission android:name="com.fsck.k9.permission.READ_ATTACHMENT"
android:permissionGroup="android.permission-group.MESSAGES"
android:protectionLevel="dangerous"

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
res/drawable/overlay_ok.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -70,6 +70,66 @@
android:textAppearance="?android:attr/textAppearanceMedium"
android:visibility="gone" />
<LinearLayout
android:id="@+id/layout_encrypt"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:paddingLeft="6dip"
android:paddingRight="6dip">
<LinearLayout
android:orientation="horizontal"
android:layout_gravity="center_vertical"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1">
<CheckBox
android:text="@string/btn_crypto_sign"
android:id="@+id/cb_crypto_signature"
android:layout_gravity="center_vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<LinearLayout
android:orientation="vertical"
android:layout_gravity="center_vertical"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:paddingRight="2dip">
<TextView
android:id="@+id/userId"
android:text=""
android:ellipsize="end"
android:textAppearance="?android:attr/textAppearanceSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/userIdRest"
android:text=""
android:textSize="10sp"
android:ellipsize="end"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</LinearLayout>
<CheckBox
android:text="@string/btn_encrypt"
android:id="@+id/cb_encrypt"
android:layout_gravity="center_vertical"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"/>
</LinearLayout>
<EditText
android:id="@+id/subject"
android:layout_width="fill_parent"

View File

@ -179,6 +179,73 @@
android:layout_height="wrap_content"
android:text="@string/message_view_show_pictures_action" />
</LinearLayout>
<LinearLayout
android:id="@+id/layout_decrypt"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:visibility="gone">
<LinearLayout
android:id="@+id/crypto_signature"
android:orientation="horizontal"
android:layout_height="wrap_content"
android:layout_width="0dip"
android:layout_weight="2"
android:gravity="center_vertical">
<RelativeLayout
android:layout_height="wrap_content"
android:layout_width="wrap_content">
<ImageView
android:id="@+id/ic_crypto_signature"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/signed_large"/>
<ImageView
android:id="@+id/ic_crypto_signature_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/overlay_error"/>
</RelativeLayout>
<LinearLayout
android:orientation="vertical"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:paddingLeft="5dip">
<TextView
android:id="@+id/userId"
android:textAppearance="?android:attr/textAppearanceMedium"
android:ellipsize="end"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"/>
<TextView
android:id="@+id/userIdRest"
android:textAppearance="?android:attr/textAppearanceSmall"
android:ellipsize="end"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"/>
</LinearLayout>
</LinearLayout>
<Button
android:text="@string/btn_decrypt"
android:id="@+id/btn_decrypt"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"/>
</LinearLayout>
<!-- Content area -->
<WebView
android:id="@+id/message_content"

View File

@ -470,7 +470,6 @@ K-9 Mail セットアップにようこそ。\nK-9 はオープンソースで
<string name="account_setup_options_mail_display_count_500">500件</string>
<string name="account_setup_options_mail_display_count_1000">1000件</string>
<string name="move_copy_cannot_copy_unsynced_message">サーバーと同期されていないメールをコピーまたは移動できません</string>
<string name="account_setup_failed_dlg_title">設定エラー</string>
@ -886,7 +885,6 @@ K-9 Mail セットアップにようこそ。\nK-9 はオープンソースで
<string name="save_or_discard_draft_message_dlg_title">メッセージの下書き保存</string>
<string name="save_or_discard_draft_message_instructions_fmt">メッセージを保存しますか?</string>
<string name="charset_not_found">このメッセージに使われている文字セット \"<xliff:g id="charset">%s</xliff:g>\" は存在していません</string>
<string name="select_text_now">選択したテキストをコピーします</string>

View File

@ -589,4 +589,14 @@
<item>5</item>
</string-array>
<string-array name="account_settings_crypto_app_entries">
<item>@string/account_settings_crypto_app_none</item>
<item>APG</item>
</string-array>
<string-array name="account_settings_crypto_app_values">
<item></item>
<item>apg</item>
</string-array>
</resources>

View File

@ -512,6 +512,12 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin
<string name="account_settings_message_lists">Listing messages</string>
<string name="account_settings_message_view">Viewing messages</string>
<string name="account_settings_quote_prefix_label">Quote prefix</string>
<string name="account_settings_crypto">Cryptography</string>
<string name="account_settings_crypto_app">OpenPGP Provider</string>
<string name="account_settings_crypto_app_none">None</string>
<string name="account_settings_crypto_app_not_available">not available</string>
<string name="account_settings_crypto_auto_signature">Auto-sign</string>
<string name="account_settings_crypto_auto_signature_summary">Use the account\'s email address to guess the signature key.</string>
<string name="account_settings_mail_check_frequency_label">Folder poll check frequency</string>
<string name="account_settings_second_class_check_frequency_label">2nd class check frequency</string>
@ -891,6 +897,20 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin
<string name="misc_preferences_attachment_title">Use Gallery bug work-around</string>
<string name="misc_preferences_attachment_description">Show buttons to add image/video attachments (to work around a Gallery 3D bug)</string>
<!-- APG related -->
<string name="error_activity_not_found">No suitable application for this action found.</string>
<string name="error_apg_version_not_supported">The installed APG version is not supported.</string>
<string name="btn_crypto_sign">Sign</string>
<string name="btn_encrypt">Encrypt</string>
<string name="btn_decrypt">Decrypt</string>
<string name="btn_verify">Verify</string>
<string name="unknown_crypto_signature_user_id">&lt;unknown&gt;</string>
<string name="key_id">id: %s</string>
<string name="insufficient_apg_permissions">K-9 doesn\'t have permission to access APG fully, please reinstall K-9 to fix that.</string>
<string name="pgp_mime_unsupported">PGP/MIME messages are not supported yet.</string>
<string name="attachment_encryption_unsupported">Warning: attachments are NOT signed or encrypted yet.</string>
<string name="send_aborted">Send aborted.</string>
<string name="save_or_discard_draft_message_dlg_title">Save draft message?</string>
<string name="save_or_discard_draft_message_instructions_fmt">Save or Discard this message?</string>

View File

@ -207,6 +207,22 @@
</PreferenceCategory>
<PreferenceCategory android:title="@string/account_settings_crypto">
<ListPreference
android:key="crypto_app"
android:title="@string/account_settings_crypto_app"
android:entries="@array/account_settings_crypto_app_entries"
android:entryValues="@array/account_settings_crypto_app_values"
android:dialogTitle="@string/account_settings_crypto_app" />
<CheckBoxPreference
android:key="crypto_auto_signature"
android:title="@string/account_settings_crypto_auto_signature"
android:summary="@string/account_settings_crypto_auto_signature_summary"
android:dependency="crypto_app"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/account_settings_notifications">
<CheckBoxPreference

View File

@ -107,6 +107,8 @@ public class Account implements BaseAccount
private boolean mRingNotified;
private String mQuotePrefix;
private boolean mSyncRemoteDeletions;
private String mCryptoApp;
private boolean mCryptoAutoSignature;
/**
* Name of the folder that was last selected for a copy or move operation.
@ -171,6 +173,8 @@ public class Account implements BaseAccount
maximumAutoDownloadMessageSize = 32768;
mQuotePrefix = DEFAULT_QUOTE_PREFIX;
mSyncRemoteDeletions = true;
mCryptoApp = "";
mCryptoAutoSignature = false;
searchableFolders = Searchable.ALL;
@ -270,14 +274,12 @@ public class Account implements BaseAccount
(random.nextInt(0x70) * 0xff) +
(random.nextInt(0x70) * 0xffff) +
0xff000000);
mLedColor = prefs.getInt(mUuid+".ledColor", mChipColor);
mVibrate = prefs.getBoolean(mUuid + ".vibrate", false);
mVibratePattern = prefs.getInt(mUuid + ".vibratePattern", 0);
mVibrateTimes = prefs.getInt(mUuid + ".vibrateTimes", 5);
mRing = prefs.getBoolean(mUuid + ".ring", true);
try
@ -356,6 +358,9 @@ public class Account implements BaseAccount
mIsSignatureBeforeQuotedText = prefs.getBoolean(mUuid + ".signatureBeforeQuotedText", false);
identities = loadIdentities(prefs);
mCryptoApp = prefs.getString(mUuid + ".cryptoApp", "");
mCryptoAutoSignature = prefs.getBoolean(mUuid + ".cryptoAutoSignature", false);
}
@ -518,6 +523,8 @@ public class Account implements BaseAccount
editor.putInt(mUuid + ".maximumPolledMessageAge", maximumPolledMessageAge);
editor.putInt(mUuid + ".maximumAutoDownloadMessageSize", maximumAutoDownloadMessageSize);
editor.putString(mUuid + ".quotePrefix", mQuotePrefix);
editor.putString(mUuid + ".cryptoApp", mCryptoApp);
editor.putBoolean(mUuid + ".cryptoAutoSignature", mCryptoAutoSignature);
for (String type : networkTypes)
{
@ -1411,6 +1418,25 @@ public class Account implements BaseAccount
mEnableMoveButtons = enableMoveButtons;
}
public String getCryptoApp()
{
return mCryptoApp;
}
public void setCryptoApp(String cryptoApp)
{
mCryptoApp = cryptoApp;
}
public boolean getCryptoAutoSignature()
{
return mCryptoAutoSignature;
}
public void setCryptoAutoSignature(boolean cryptoAutoSignature)
{
mCryptoAutoSignature = cryptoAutoSignature;
}
public synchronized boolean syncRemoteDeletions()
{
return mSyncRemoteDeletions;

View File

@ -1,6 +1,16 @@
package com.fsck.k9.activity;
import java.io.File;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.StringTokenizer;
import org.apache.james.mime4j.codec.EncoderUtil;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ContentResolver;
@ -26,25 +36,42 @@ import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
import android.view.Window;
import android.widget.AutoCompleteTextView.Validator;
import android.widget.*;
import com.fsck.k9.*;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.MultiAutoCompleteTextView;
import android.widget.TextView;
import android.widget.Toast;
import com.fsck.k9.Account;
import com.fsck.k9.EmailAddressAdapter;
import com.fsck.k9.EmailAddressValidator;
import com.fsck.k9.Identity;
import com.fsck.k9.K9;
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.helper.Utility;
import com.fsck.k9.mail.*;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.mail.internet.*;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Multipart;
import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.internet.MimeBodyPart;
import com.fsck.k9.mail.internet.MimeHeader;
import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.mail.internet.MimeMultipart;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.internet.TextBody;
import com.fsck.k9.mail.store.LocalStore;
import com.fsck.k9.mail.store.LocalStore.LocalAttachmentBody;
import java.io.File;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.StringTokenizer;
import org.apache.james.mime4j.codec.EncoderUtil;
public class MessageCompose extends K9Activity implements OnClickListener, OnFocusChangeListener
{
@ -55,8 +82,8 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
private static final String ACTION_FORWARD = "com.fsck.k9.intent.action.FORWARD";
private static final String ACTION_EDIT_DRAFT = "com.fsck.k9.intent.action.EDIT_DRAFT";
private static final String EXTRA_ACCOUNT = "account";
private static final String EXTRA_MESSAGE_BODY = "messageBody";
private static final String EXTRA_MESSAGE_REFERENCE = "message_reference";
private static final String STATE_KEY_ATTACHMENTS =
@ -75,6 +102,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
"com.fsck.k9.activity.MessageCompose.identityChanged";
private static final String STATE_IDENTITY =
"com.fsck.k9.activity.MessageCompose.identity";
private static final String STATE_CRYPTO = "crypto";
private static final String STATE_IN_REPLY_TO = "com.fsck.k9.activity.MessageCompose.inReplyTo";
private static final String STATE_REFERENCES = "com.fsck.k9.activity.MessageCompose.references";
@ -110,6 +138,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
private MessageReference mMessageReference;
private Message mSourceMessage;
private String mSourceMessageBody;
/**
* Indicates that the source message has been processed at least once and should not
@ -130,11 +159,19 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
private View mQuotedTextBar;
private ImageButton mQuotedTextDelete;
private EditText mQuotedText;
private View mEncryptLayout;
private CheckBox mCryptoSignatureCheckbox;
private CheckBox mEncryptCheckbox;
private TextView mCryptoSignatureUserId;
private TextView mCryptoSignatureUserIdRest;
private CryptoProvider mCrypto = null;
private String mReferences;
private String mInReplyTo;
private boolean mDraftNeedsSaving = false;
private boolean mPreventDraftSaving = false;
/**
* The draft uid of this message. This is used when saving drafts so that the same draft is
@ -220,14 +257,17 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
* @param account
* @param message
* @param replyAll
* @param messageBody optional, for decrypted messages, null if it should be grabbed from the given message
*/
public static void actionReply(
Context context,
Account account,
Message message,
boolean replyAll)
boolean replyAll,
String messageBody)
{
Intent i = new Intent(context, MessageCompose.class);
i.putExtra(EXTRA_MESSAGE_BODY, messageBody);
i.putExtra(EXTRA_MESSAGE_REFERENCE, message.makeMessageReference());
if (replyAll)
{
@ -245,10 +285,16 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
* @param context
* @param account
* @param message
* @param messageBody optional, for decrypted messages, null if it should be grabbed from the given message
*/
public static void actionForward(Context context, Account account, Message message)
public static void actionForward(
Context context,
Account account,
Message message,
String messageBody)
{
Intent i = new Intent(context, MessageCompose.class);
i.putExtra(EXTRA_MESSAGE_BODY, messageBody);
i.putExtra(EXTRA_MESSAGE_REFERENCE, message.makeMessageReference());
i.setAction(ACTION_FORWARD);
context.startActivity(i);
@ -282,6 +328,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
final Intent intent = getIntent();
mMessageReference = (MessageReference) intent.getSerializableExtra(EXTRA_MESSAGE_REFERENCE);
mSourceMessageBody = (String) intent.getStringExtra(EXTRA_MESSAGE_BODY);
final String accountUuid = (mMessageReference != null) ?
mMessageReference.accountUuid :
@ -613,9 +660,113 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
mMessageContentView.requestFocus();
}
mEncryptLayout = (View)findViewById(R.id.layout_encrypt);
mCryptoSignatureCheckbox = (CheckBox)findViewById(R.id.cb_crypto_signature);
mCryptoSignatureUserId = (TextView)findViewById(R.id.userId);
mCryptoSignatureUserIdRest = (TextView)findViewById(R.id.userIdRest);
mEncryptCheckbox = (CheckBox)findViewById(R.id.cb_encrypt);
initializeCrypto();
if (mCrypto.isAvailable(this))
{
mEncryptLayout.setVisibility(View.VISIBLE);
mCryptoSignatureCheckbox.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
CheckBox checkBox = (CheckBox) v;
if (checkBox.isChecked())
{
mPreventDraftSaving = true;
if (!mCrypto.selectSecretKey(MessageCompose.this))
{
mPreventDraftSaving = false;
}
checkBox.setChecked(false);
}
else
{
mCrypto.setSignatureKeyId(0);
updateEncryptLayout();
}
}
});
if (mAccount.getCryptoAutoSignature())
{
long ids[] = mCrypto.getSecretKeyIdsFromEmail(this, mIdentity.getEmail());
if (ids != null && ids.length > 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");
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(

View File

@ -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)

View File

@ -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<MessageReference> 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);
@ -812,6 +853,9 @@ public class MessageView extends K9Activity implements OnClickListener
{
mMessageReference = (MessageReference)icicle.getSerializable(EXTRA_MESSAGE_REFERENCE);
mMessageReferences = (ArrayList<MessageReference>)icicle.getSerializable(EXTRA_MESSAGE_REFERENCES);
mCrypto = (CryptoProvider) icicle.getSerializable(STATE_CRYPTO);
updateDecryptLayout();
}
else
{
@ -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,6 +2076,14 @@ public class MessageView extends K9Activity implements OnClickListener
mHandler.removeAllAttachments();
String text;
String type = "text/html";
if (mCrypto.getDecryptedData() != null)
{
text = mCrypto.getDecryptedData();
type = "text/plain";
}
else
{
Part part = MimeUtility.findFirstPartByMimeType(mMessage, "text/html");
if (part == null)
{
@ -2032,6 +2109,7 @@ public class MessageView extends K9Activity implements OnClickListener
{
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("<img"));
@ -2056,6 +2136,7 @@ public class MessageView extends K9Activity implements OnClickListener
public void run()
{
mMessageContentView.loadUrl("file:///android_asset/empty.html");
updateDecryptLayout();
}
});
}
@ -2099,6 +2180,7 @@ public class MessageView extends K9Activity implements OnClickListener
!MessageView.this.mMessage.isSet(Flag.X_DOWNLOADED_PARTIAL))
{
mMessageContentView.loadUrl("file:///android_asset/empty.html");
updateDecryptLayout();
}
}
});
@ -2137,6 +2219,7 @@ public class MessageView extends K9Activity implements OnClickListener
public void run()
{
mMessageContentView.loadUrl("file:///android_asset/loading.html");
updateDecryptLayout();
setProgressBarIndeterminateVisibility(true);
}
});
@ -2293,7 +2376,118 @@ public class MessageView extends K9Activity implements OnClickListener
return slide;
}
private void initializeCrypto()
{
if (mCrypto != null)
{
return;
}
if (mAccount == null)
{
mAccount = Preferences.getPreferences(this).getAccount(mMessageReference.accountUuid);
}
mCrypto = CryptoProvider.createInstance(mAccount);
}
/**
* Fill the decrypt layout with signature data, if known, make controls visible, if
* they should be visible.
*/
public void updateDecryptLayout()
{
if (mCrypto.getSignatureKeyId() != 0)
{
mCryptoSignatureUserIdRest.setText(
getString(R.string.key_id, Long.toHexString(mCrypto.getSignatureKeyId() & 0xffffffffL)));
String userId = mCrypto.getSignatureUserId();
if (userId == null)
{
userId = getString(R.string.unknown_crypto_signature_user_id);
}
String chunks[] = userId.split(" <", 2);
String name = chunks[0];
if (chunks.length > 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.
*/

View File

@ -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()));

View File

@ -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<Long> keyIds = new Vector<Long>();
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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}