Merge pull request #1501 from k9mail/decrypted-reply

Decrypted reply
This commit is contained in:
cketti 2016-07-24 16:06:48 +02:00 committed by GitHub
commit df761b9344
16 changed files with 238 additions and 152 deletions

View file

@ -113,8 +113,8 @@ public class MessageCompose extends K9Activity implements OnClickListener,
public static final String ACTION_EDIT_DRAFT = "com.fsck.k9.intent.action.EDIT_DRAFT";
public static final String EXTRA_ACCOUNT = "account";
public static final String EXTRA_MESSAGE_BODY = "messageBody";
public static final String EXTRA_MESSAGE_REFERENCE = "message_reference";
public static final String EXTRA_MESSAGE_DECRYPTION_RESULT = "message_decryption_result";
private static final String STATE_KEY_SOURCE_MESSAGE_PROCED =
"com.fsck.k9.activity.MessageCompose.stateKeySourceMessageProced";
@ -133,7 +133,6 @@ public class MessageCompose extends K9Activity implements OnClickListener,
private static final int MSG_PROGRESS_ON = 1;
private static final int MSG_PROGRESS_OFF = 2;
private static final int MSG_SKIPPED_ATTACHMENTS = 3;
public static final int MSG_SAVED_DRAFT = 4;
private static final int MSG_DISCARDED_DRAFT = 5;
@ -278,12 +277,6 @@ public class MessageCompose extends K9Activity implements OnClickListener,
case MSG_PROGRESS_OFF:
setProgressBarIndeterminateVisibility(false);
break;
case MSG_SKIPPED_ATTACHMENTS:
Toast.makeText(
MessageCompose.this,
getString(R.string.message_compose_attachments_skipped_toast),
Toast.LENGTH_LONG).show();
break;
case MSG_SAVED_DRAFT:
mDraftId = (Long) msg.obj;
Toast.makeText(
@ -373,10 +366,8 @@ public class MessageCompose extends K9Activity implements OnClickListener,
EolConvertingEditText upperSignature = (EolConvertingEditText)findViewById(R.id.upper_signature);
EolConvertingEditText lowerSignature = (EolConvertingEditText)findViewById(R.id.lower_signature);
String sourceMessageBody = intent.getStringExtra(EXTRA_MESSAGE_BODY);
QuotedMessageMvpView quotedMessageMvpView = new QuotedMessageMvpView(this);
quotedMessagePresenter = new QuotedMessagePresenter(this, quotedMessageMvpView, mAccount, sourceMessageBody);
quotedMessagePresenter = new QuotedMessagePresenter(this, quotedMessageMvpView, mAccount);
attachmentPresenter = new AttachmentPresenter(getApplicationContext(), attachmentMvpView, getLoaderManager());
mMessageContentView = (EolConvertingEditText)findViewById(R.id.message_content);
@ -474,7 +465,9 @@ public class MessageCompose extends K9Activity implements OnClickListener,
messageLoaderHelper = new MessageLoaderHelper(this, getLoaderManager(), getFragmentManager(),
messageLoaderCallbacks);
mHandler.sendEmptyMessage(MSG_PROGRESS_ON);
messageLoaderHelper.asyncStartOrResumeLoadingMessage(mMessageReference);
Parcelable cachedDecryptionResult = intent.getParcelableExtra(EXTRA_MESSAGE_DECRYPTION_RESULT);
messageLoaderHelper.asyncStartOrResumeLoadingMessage(mMessageReference, cachedDecryptionResult);
}
if (mAction != Action.EDIT_DRAFT) {
@ -1139,30 +1132,30 @@ public class MessageCompose extends K9Activity implements OnClickListener,
throw new IllegalStateException("tried to edit quoted message with no referenced message");
}
messageLoaderHelper.asyncStartOrResumeLoadingMessage(mMessageReference);
messageLoaderHelper.asyncStartOrResumeLoadingMessage(mMessageReference, null);
}
/**
* Pull out the parts of the now loaded source message and apply them to the new message
* depending on the type of message being composed.
*
* @param message
* @param messageViewInfo
* The source message used to populate the various text fields.
*/
private void processSourceMessage(LocalMessage message) {
private void processSourceMessage(MessageViewInfo messageViewInfo) {
try {
switch (mAction) {
case REPLY:
case REPLY_ALL: {
processMessageToReplyTo(message);
processMessageToReplyTo(messageViewInfo);
break;
}
case FORWARD: {
processMessageToForward(message);
processMessageToForward(messageViewInfo);
break;
}
case EDIT_DRAFT: {
processDraftMessage(message);
processDraftMessage(messageViewInfo);
break;
}
default: {
@ -1184,7 +1177,9 @@ public class MessageCompose extends K9Activity implements OnClickListener,
updateMessageFormat();
}
private void processMessageToReplyTo(Message message) throws MessagingException {
private void processMessageToReplyTo(MessageViewInfo messageViewInfo) throws MessagingException {
Message message = messageViewInfo.message;
if (message.getSubject() != null) {
final String subject = PREFIX.matcher(message.getSubject()).replaceFirst("");
@ -1221,7 +1216,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
}
// Quote the message and setup the UI.
quotedMessagePresenter.initFromReplyToMessage(message, mAction);
quotedMessagePresenter.initFromReplyToMessage(messageViewInfo, mAction);
if (mAction == Action.REPLY || mAction == Action.REPLY_ALL) {
Identity useIdentity = IdentityHelper.getRecipientIdentityFromMessage(mAccount, message);
@ -1233,7 +1228,9 @@ public class MessageCompose extends K9Activity implements OnClickListener,
}
private void processMessageToForward(Message message) throws MessagingException {
private void processMessageToForward(MessageViewInfo messageViewInfo) throws MessagingException {
Message message = messageViewInfo.message;
String subject = message.getSubject();
if (subject != null && !subject.toLowerCase(Locale.US).startsWith("fwd:")) {
mSubjectView.setText("Fwd: " + subject);
@ -1255,16 +1252,12 @@ public class MessageCompose extends K9Activity implements OnClickListener,
}
// Quote the message and setup the UI.
quotedMessagePresenter.processMessageToForward(message);
if (!mSourceMessageProcessed) {
if (message.isSet(Flag.X_DOWNLOADED_PARTIAL) || !attachmentPresenter.loadAttachments(message, 0)) {
mHandler.sendEmptyMessage(MSG_SKIPPED_ATTACHMENTS);
}
}
quotedMessagePresenter.processMessageToForward(messageViewInfo);
attachmentPresenter.processMessageToForward(messageViewInfo);
}
private void processDraftMessage(LocalMessage message) throws MessagingException {
private void processDraftMessage(MessageViewInfo messageViewInfo) throws MessagingException {
Message message = messageViewInfo.message;
mDraftId = MessagingController.getInstance(getApplication()).getId(message);
mSubjectView.setText(message.getSubject());
@ -1301,7 +1294,9 @@ public class MessageCompose extends K9Activity implements OnClickListener,
newIdentity.setSignature(k9identity.get(IdentityField.SIGNATURE));
mSignatureChanged = true;
} else {
newIdentity.setSignatureUse(message.getFolder().getSignatureUse());
if (message instanceof LocalMessage) {
newIdentity.setSignatureUse(((LocalMessage) message).getFolder().getSignatureUse());
}
newIdentity.setSignature(mIdentity.getSignature());
}
@ -1341,7 +1336,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
updateSignature();
updateFrom();
quotedMessagePresenter.processDraftMessage(message, k9identity);
quotedMessagePresenter.processDraftMessage(messageViewInfo, k9identity);
}
static class SendMessageTask extends AsyncTask<Void, Void, Void> {
@ -1524,14 +1519,14 @@ public class MessageCompose extends K9Activity implements OnClickListener,
}
}
public void loadLocalMessageForDisplay(LocalMessage message, Action action) {
public void loadLocalMessageForDisplay(MessageViewInfo messageViewInfo, Action action) {
// We check to see if we've previously processed the source message since this
// could be called when switching from HTML to text replies. If that happens, we
// only want to update the UI with quoted text (which picks the appropriate
// part).
if (mSourceMessageProcessed) {
try {
quotedMessagePresenter.populateUIWithQuotedMessage(message, true, action);
quotedMessagePresenter.populateUIWithQuotedMessage(messageViewInfo, true, action);
} catch (MessagingException e) {
// Hm, if we couldn't populate the UI after source reprocessing, let's just delete it?
quotedMessagePresenter.showOrHideQuotedText(QuotedTextMode.HIDE);
@ -1539,7 +1534,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
}
updateMessageFormat();
} else {
processSourceMessage(message);
processSourceMessage(messageViewInfo);
mSourceMessageProcessed = true;
}
}
@ -1559,7 +1554,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
@Override
public void onMessageViewInfoLoadFinished(LocalMessage localMessage, MessageViewInfo messageViewInfo) {
mHandler.sendEmptyMessage(MSG_PROGRESS_OFF);
loadLocalMessageForDisplay(localMessage, mAction);
loadLocalMessageForDisplay(messageViewInfo, mAction);
}
@Override
@ -1731,6 +1726,12 @@ public class MessageCompose extends K9Activity implements OnClickListener,
public void performSaveAfterChecks() {
MessageCompose.this.performSaveAfterChecks();
}
@Override
public void showMissingAttachmentsPartialMessageWarning() {
Toast.makeText(MessageCompose.this,
getString(R.string.message_compose_attachments_skipped_toast), Toast.LENGTH_LONG).show();
}
};
}

View file

@ -18,6 +18,7 @@ import android.content.res.Configuration;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@ -53,7 +54,6 @@ import com.fsck.k9.search.SearchSpecification;
import com.fsck.k9.search.SearchSpecification.Attribute;
import com.fsck.k9.search.SearchSpecification.SearchCondition;
import com.fsck.k9.search.SearchSpecification.SearchField;
import com.fsck.k9.ui.messageview.CryptoInfoDialog.OnClickShowCryptoKeyListener;
import com.fsck.k9.ui.messageview.MessageViewFragment;
import com.fsck.k9.ui.messageview.MessageViewFragment.MessageViewFragmentListener;
import com.fsck.k9.view.MessageHeader;
@ -1210,17 +1210,32 @@ public class MessageList extends K9Activity implements MessageListFragmentListen
@Override
public void onForward(LocalMessage message) {
MessageActions.actionForward(this, message, null);
onForward(message, null);
}
@Override
public void onForward(LocalMessage message, Parcelable decryptionResultForReply) {
MessageActions.actionForward(this, message, decryptionResultForReply);
}
@Override
public void onReply(LocalMessage message) {
MessageActions.actionReply(this, message, false, null);
onReply(message, null);
}
@Override
public void onReply(LocalMessage message, Parcelable decryptionResultForReply) {
MessageActions.actionReply(this, message, false, decryptionResultForReply);
}
@Override
public void onReplyAll(LocalMessage message) {
MessageActions.actionReply(this, message, true, null);
onReplyAll(message, null);
}
@Override
public void onReplyAll(LocalMessage message, Parcelable decryptionResultForReply) {
MessageActions.actionReply(this, message, true, decryptionResultForReply);
}
@Override

View file

@ -9,6 +9,7 @@ import android.content.Intent;
import android.content.IntentSender;
import android.content.Loader;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
@ -28,6 +29,7 @@ import com.fsck.k9.ui.crypto.MessageCryptoCallback;
import com.fsck.k9.ui.crypto.MessageCryptoHelper;
import com.fsck.k9.ui.message.LocalMessageExtractorLoader;
import com.fsck.k9.ui.message.LocalMessageLoader;
import org.openintents.openpgp.OpenPgpDecryptionResult;
/** This class is responsible for loading a message start to finish, and
@ -83,6 +85,7 @@ public class MessageLoaderHelper {
private LocalMessage localMessage;
private MessageCryptoAnnotations messageCryptoAnnotations;
private OpenPgpDecryptionResult cachedDecryptionResult;
private MessageCryptoHelper messageCryptoHelper;
@ -99,10 +102,18 @@ public class MessageLoaderHelper {
// public interface
@UiThread
public void asyncStartOrResumeLoadingMessage(MessageReference messageReference) {
public void asyncStartOrResumeLoadingMessage(MessageReference messageReference, Parcelable cachedDecryptionResult) {
this.messageReference = messageReference;
this.account = Preferences.getPreferences(context).getAccount(messageReference.getAccountUuid());
if (cachedDecryptionResult != null) {
if (cachedDecryptionResult instanceof OpenPgpDecryptionResult) {
this.cachedDecryptionResult = (OpenPgpDecryptionResult) cachedDecryptionResult;
} else {
Log.e(K9.LOG_TAG, "Got decryption result of unknown type - ignoring");
}
}
startOrResumeLocalMessageLoader();
}
@ -248,7 +259,8 @@ public class MessageLoaderHelper {
messageCryptoHelper = new MessageCryptoHelper(context, account.getOpenPgpProvider());
retainCryptoHelperFragment.setData(messageCryptoHelper);
}
messageCryptoHelper.asyncStartOrResumeProcessingMessage(localMessage, messageCryptoCallback);
messageCryptoHelper.asyncStartOrResumeProcessingMessage(
localMessage, messageCryptoCallback, cachedDecryptionResult);
}
private void cancelAndClearCryptoOperation() {

View file

@ -21,11 +21,14 @@ import com.fsck.k9.activity.loader.AttachmentContentLoader;
import com.fsck.k9.activity.loader.AttachmentInfoLoader;
import com.fsck.k9.activity.misc.Attachment;
import com.fsck.k9.activity.misc.Attachment.LoadingState;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Multipart;
import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mailstore.AttachmentViewInfo;
import com.fsck.k9.mailstore.LocalBodyPart;
import com.fsck.k9.mailstore.MessageViewInfo;
import com.fsck.k9.provider.AttachmentProvider;
@ -130,31 +133,74 @@ public class AttachmentPresenter {
addAttachment(uri, null);
}
public void addAttachment(Uri uri, String contentType) {
int loaderId = getNextFreeLoaderId();
Attachment attachment = Attachment.createAttachment(uri, loaderId, contentType);
public void addAttachment(AttachmentViewInfo attachmentViewInfo) {
if (attachments.containsKey(attachmentViewInfo.uri)) {
throw new IllegalStateException("Received the same attachmentViewInfo twice!");
}
int loaderId = getNextFreeLoaderId();
Attachment attachment = Attachment.createAttachment(
attachmentViewInfo.uri, loaderId, attachmentViewInfo.mimeType);
attachment = attachment.deriveWithMetadataLoaded(
attachmentViewInfo.mimeType, attachmentViewInfo.displayName, attachmentViewInfo.size);
addAttachmentAndStartLoader(attachment);
}
public void addAttachment(Uri uri, String contentType) {
if (attachments.containsKey(uri)) {
return;
}
attachments.put(uri, attachment);
int loaderId = getNextFreeLoaderId();
Attachment attachment = Attachment.createAttachment(uri, loaderId, contentType);
addAttachmentAndStartLoader(attachment);
}
public void processMessageToForward(MessageViewInfo messageViewInfo) {
if (messageViewInfo.message.isSet(Flag.X_DOWNLOADED_PARTIAL)) {
attachmentMvpView.showMissingAttachmentsPartialMessageWarning();
return;
}
for (AttachmentViewInfo attachmentViewInfo : messageViewInfo.attachments) {
if (attachmentViewInfo.firstClassAttachment) {
addAttachment(attachmentViewInfo);
}
}
}
private void addAttachmentAndStartLoader(Attachment attachment) {
attachments.put(attachment.uri, attachment);
attachmentMvpView.addAttachmentView(attachment);
initAttachmentInfoLoader(attachment);
if (attachment.state == LoadingState.URI_ONLY) {
initAttachmentInfoLoader(attachment);
} else if (attachment.state == LoadingState.METADATA) {
initAttachmentContentLoader(attachment);
} else {
throw new IllegalStateException("Attachment can only be added in URI_ONLY or METADATA state!");
}
}
private void initAttachmentInfoLoader(Attachment attachment) {
if (attachment.state != LoadingState.URI_ONLY) {
throw new IllegalStateException("initAttachmentInfoLoader can only be called for URI_ONLY state!");
}
Bundle bundle = new Bundle();
bundle.putParcelable(LOADER_ARG_ATTACHMENT, attachment.uri);
loaderManager.initLoader(attachment.loaderId, bundle, mAttachmentInfoLoaderCallback);
}
private void initAttachmentContentLoader(Attachment attachment) {
if (attachment.state != LoadingState.METADATA) {
throw new IllegalStateException("initAttachmentContentLoader can only be called for METADATA state!");
}
Bundle bundle = new Bundle();
bundle.putParcelable(LOADER_ARG_ATTACHMENT, attachment.uri);
loaderManager.initLoader(attachment.loaderId, bundle, mAttachmentContentLoaderCallback);
}
@ -364,5 +410,6 @@ public class AttachmentPresenter {
void performSendAfterChecks();
void performSaveAfterChecks();
void showMissingAttachmentsPartialMessageWarning();
}
}

View file

@ -2,6 +2,7 @@ package com.fsck.k9.activity.compose;
import android.content.Context;
import android.content.Intent;
import android.os.Parcelable;
import com.fsck.k9.Account;
import com.fsck.k9.Preferences;
@ -28,15 +29,11 @@ public class MessageActions {
/**
* Get intent for composing a new message as a reply to the given message. If replyAll is true
* the function is reply all instead of simply reply.
* @param messageBody optional, for decrypted messages, null if it should be grabbed from the given message
*/
public static Intent getActionReplyIntent(
Context context,
LocalMessage message,
boolean replyAll,
String messageBody) {
Context context, LocalMessage message, boolean replyAll, Parcelable decryptionResult) {
Intent i = new Intent(context, MessageCompose.class);
i.putExtra(MessageCompose.EXTRA_MESSAGE_BODY, messageBody);
i.putExtra(MessageCompose.EXTRA_MESSAGE_DECRYPTION_RESULT, decryptionResult);
i.putExtra(MessageCompose.EXTRA_MESSAGE_REFERENCE, message.makeMessageReference());
if (replyAll) {
i.setAction(MessageCompose.ACTION_REPLY_ALL);
@ -58,27 +55,19 @@ public class MessageActions {
/**
* Compose a new message as a reply to the given message. If replyAll is true the function
* is reply all instead of simply reply.
* @param messageBody optional, for decrypted messages, null if it should be grabbed from the given message
*/
public static void actionReply(
Context context,
LocalMessage message,
boolean replyAll,
String messageBody) {
context.startActivity(getActionReplyIntent(context, message, replyAll, messageBody));
Context context, LocalMessage message, boolean replyAll, Parcelable decryptionResult) {
context.startActivity(getActionReplyIntent(context, message, replyAll, decryptionResult));
}
/**
* Compose a new message as a forward of the given message.
* @param messageBody optional, for decrypted messages, null if it should be grabbed from the given message
*/
public static void actionForward(
Context context,
LocalMessage message,
String messageBody) {
public static void actionForward(Context context, LocalMessage message, Parcelable decryptionResult) {
Intent i = new Intent(context, MessageCompose.class);
i.putExtra(MessageCompose.EXTRA_MESSAGE_BODY, messageBody);
i.putExtra(MessageCompose.EXTRA_MESSAGE_REFERENCE, message.makeMessageReference());
i.putExtra(MessageCompose.EXTRA_MESSAGE_DECRYPTION_RESULT, decryptionResult);
i.setAction(MessageCompose.ACTION_FORWARD);
context.startActivity(i);
}

View file

@ -191,12 +191,12 @@ public class RecipientPresenter implements PermissionPingCallback {
outState.putBoolean(STATE_KEY_CRYPTO_ENABLE_PGP_INLINE, cryptoEnablePgpInline);
}
public void initFromDraftMessage(LocalMessage message) {
public void initFromDraftMessage(Message message) {
initRecipientsFromDraftMessage(message);
initPgpInlineFromDraftMessage(message);
}
private void initRecipientsFromDraftMessage(LocalMessage message) {
private void initRecipientsFromDraftMessage(Message message) {
addToAddresses(message.getRecipients(RecipientType.TO));
Address[] ccRecipients = message.getRecipients(RecipientType.CC);
@ -206,7 +206,7 @@ public class RecipientPresenter implements PermissionPingCallback {
addBccAddresses(bccRecipients);
}
private void initPgpInlineFromDraftMessage(LocalMessage message) {
private void initPgpInlineFromDraftMessage(Message message) {
cryptoEnablePgpInline = message.isSet(Flag.X_DRAFT_OPENPGP_INLINE);
}

View file

@ -75,19 +75,17 @@ public class QuotedMessageHelper {
InsertableHtmlContent insertable = findInsertionPoints(messageBody);
String sentDate = getSentDateText(resources, originalMessage);
String fromAddress = Address.toString(originalMessage.getFrom());
if (quoteStyle == QuoteStyle.PREFIX) {
StringBuilder header = new StringBuilder(QUOTE_BUFFER_LENGTH);
header.append("<div class=\"gmail_quote\">");
if (sentDate.length() != 0) {
header.append(HtmlConverter.textToHtmlFragment(String.format(
resources.getString(R.string.message_compose_reply_header_fmt_with_date),
sentDate,
Address.toString(originalMessage.getFrom()))
resources.getString(R.string.message_compose_reply_header_fmt_with_date), sentDate, fromAddress)
));
} else {
header.append(HtmlConverter.textToHtmlFragment(String.format(
resources.getString(R.string.message_compose_reply_header_fmt),
Address.toString(originalMessage.getFrom()))
resources.getString(R.string.message_compose_reply_header_fmt), fromAddress)
));
}
header.append("<blockquote class=\"gmail_quote\" " +
@ -102,9 +100,9 @@ public class QuotedMessageHelper {
StringBuilder header = new StringBuilder();
header.append("<div style='font-size:10.0pt;font-family:\"Tahoma\",\"sans-serif\";padding:3.0pt 0in 0in 0in'>\r\n");
header.append("<hr style='border:none;border-top:solid #E1E1E1 1.0pt'>\r\n"); // This gets converted into a horizontal line during html to text conversion.
if (originalMessage.getFrom() != null && Address.toString(originalMessage.getFrom()).length() != 0) {
if (originalMessage.getFrom() != null && fromAddress.length() != 0) {
header.append("<b>").append(resources.getString(R.string.message_compose_quote_header_from)).append("</b> ")
.append(HtmlConverter.textToHtmlFragment(Address.toString(originalMessage.getFrom())))
.append(HtmlConverter.textToHtmlFragment(fromAddress))
.append("<br>\r\n");
}
if (sentDate.length() != 0) {

View file

@ -8,6 +8,7 @@ import java.util.List;
import android.content.Context;
import android.support.annotation.VisibleForTesting;
import android.support.annotation.WorkerThread;
import com.fsck.k9.R;
import com.fsck.k9.helper.HtmlConverter;
@ -44,6 +45,7 @@ public class MessageViewInfoExtractor {
private MessageViewInfoExtractor() { }
@WorkerThread
public static MessageViewInfo extractMessageForView(Context context,
Message message, MessageCryptoAnnotations annotations) throws MessagingException {

View file

@ -11,6 +11,7 @@ import android.net.Uri;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
import android.support.annotation.WorkerThread;
import com.fsck.k9.Globals;
import com.fsck.k9.K9;
@ -40,7 +41,9 @@ public class AttachmentInfoExtractor {
this.context = context;
}
public List<AttachmentViewInfo> extractAttachmentInfos(List<Part> attachmentParts) throws MessagingException {
@WorkerThread
public List<AttachmentViewInfo> extractAttachmentInfos(List<Part> attachmentParts)
throws MessagingException {
List<AttachmentViewInfo> attachments = new ArrayList<>();
for (Part part : attachmentParts) {
@ -50,6 +53,7 @@ public class AttachmentInfoExtractor {
return attachments;
}
@WorkerThread
public AttachmentViewInfo extractAttachmentInfo(Part part) throws MessagingException {
Uri uri;
long size;
@ -93,6 +97,7 @@ public class AttachmentInfoExtractor {
return extractAttachmentInfo(part, Uri.EMPTY, AttachmentViewInfo.UNKNOWN_SIZE);
}
@WorkerThread
private AttachmentViewInfo extractAttachmentInfo(Part part, Uri uri, long size) throws MessagingException {
boolean firstClassAttachment = true;
@ -128,6 +133,7 @@ public class AttachmentInfoExtractor {
return new AttachmentViewInfo(mimeType, name, attachmentSize, uri, firstClassAttachment, part);
}
@WorkerThread
private long extractAttachmentSize(String contentDisposition, long size) {
if (size != AttachmentViewInfo.UNKNOWN_SIZE) {
return size;

View file

@ -15,13 +15,12 @@ import com.fsck.k9.activity.MessageCompose;
import com.fsck.k9.activity.MessageCompose.Action;
import com.fsck.k9.helper.HtmlConverter;
import com.fsck.k9.helper.QuotedMessageHelper;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.internet.MessageExtractor;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mailstore.AttachmentResolver;
import com.fsck.k9.mailstore.LocalMessage;
import com.fsck.k9.mailstore.MessageViewInfo;
import com.fsck.k9.message.IdentityField;
import com.fsck.k9.message.InsertableHtmlContent;
import com.fsck.k9.message.MessageBuilder;
@ -46,33 +45,17 @@ public class QuotedMessagePresenter {
private QuoteStyle quoteStyle;
private boolean forcePlainText;
/**
* "Original" message body
*
* <p>
* The contents of this string will be used instead of the body of a referenced message when
* replying to or forwarding a message.<br>
* Right now this is only used when replying to a signed or encrypted message. It then contains
* the stripped/decrypted body of that message.
* </p>
* <p><strong>Note:</strong>
* When this field is not {@code null} we assume that the message we are composing right now
* should be encrypted.
* </p>
*/
private String sourceMessageBody;
private SimpleMessageFormat quotedTextFormat;
private InsertableHtmlContent quotedHtmlContent;
private Account account;
public QuotedMessagePresenter(MessageCompose messageCompose, QuotedMessageMvpView quotedMessageMvpView,
Account account, String sourceMessageBody) {
public QuotedMessagePresenter(
MessageCompose messageCompose, QuotedMessageMvpView quotedMessageMvpView, Account account) {
this.messageCompose = messageCompose;
this.resources = messageCompose.getResources();
this.view = quotedMessageMvpView;
this.sourceMessageBody = sourceMessageBody;
onSwitchAccount(account);
quotedTextMode = QuotedTextMode.NONE;
@ -96,7 +79,7 @@ public class QuotedMessagePresenter {
* @param showQuotedText
* {@code true} if the quoted text should be shown, {@code false} otherwise.
*/
public void populateUIWithQuotedMessage(Message sourceMessage, boolean showQuotedText, Action action)
public void populateUIWithQuotedMessage(MessageViewInfo messageViewInfo, boolean showQuotedText, Action action)
throws MessagingException {
MessageFormat origMessageFormat = account.getMessageFormat();
@ -107,18 +90,15 @@ public class QuotedMessagePresenter {
// Figure out which message format to use for the quoted text by looking if the source
// message contains a text/html part. If it does, we use that.
quotedTextFormat =
(MimeUtility.findFirstPartByMimeType(sourceMessage, "text/html") == null) ?
(MimeUtility.findFirstPartByMimeType(messageViewInfo.rootPart, "text/html") == null) ?
SimpleMessageFormat.TEXT : SimpleMessageFormat.HTML;
} else {
quotedTextFormat = SimpleMessageFormat.HTML;
}
// TODO -- I am assuming that sourceMessageBody will always be a text part. Is this a safe assumption?
// Handle the original message in the reply
// If we already have sourceMessageBody, use that. It's pre-populated if we've got crypto going on.
String content = sourceMessageBody != null ? sourceMessageBody :
QuotedMessageHelper.getBodyTextFromMessage(sourceMessage, quotedTextFormat);
String content = QuotedMessageHelper.getBodyTextFromMessage(messageViewInfo.rootPart, quotedTextFormat);
if (quotedTextFormat == SimpleMessageFormat.HTML) {
// Strip signature.
@ -129,15 +109,15 @@ public class QuotedMessagePresenter {
// Add the HTML reply header to the top of the content.
quotedHtmlContent = QuotedMessageHelper.quoteOriginalHtmlMessage(
resources, sourceMessage, content, quoteStyle);
resources, messageViewInfo.message, content, quoteStyle);
// Load the message with the reply header. TODO replace with MessageViewInfo data
view.setQuotedHtml(quotedHtmlContent.getQuotedContent(),
AttachmentResolver.createFromPart(sourceMessage));
AttachmentResolver.createFromPart(messageViewInfo.rootPart));
// TODO: Also strip the signature from the text/plain part
view.setQuotedText(QuotedMessageHelper.quoteOriginalTextMessage(resources, sourceMessage,
QuotedMessageHelper.getBodyTextFromMessage(sourceMessage, SimpleMessageFormat.TEXT),
view.setQuotedText(QuotedMessageHelper.quoteOriginalTextMessage(resources, messageViewInfo.message,
QuotedMessageHelper.getBodyTextFromMessage(messageViewInfo.rootPart, SimpleMessageFormat.TEXT),
quoteStyle, account.getQuotePrefix()));
} else if (quotedTextFormat == SimpleMessageFormat.TEXT) {
@ -146,7 +126,7 @@ public class QuotedMessagePresenter {
}
view.setQuotedText(QuotedMessageHelper.quoteOriginalTextMessage(
resources, sourceMessage, content, quoteStyle, account.getQuotePrefix()));
resources, messageViewInfo.message, content, quoteStyle, account.getQuotePrefix()));
}
if (showQuotedText) {
@ -186,17 +166,17 @@ public class QuotedMessagePresenter {
(QuotedTextMode) savedInstanceState.getSerializable(STATE_KEY_QUOTED_TEXT_MODE));
}
public void processMessageToForward(Message message) throws MessagingException {
public void processMessageToForward(MessageViewInfo messageViewInfo) throws MessagingException {
quoteStyle = QuoteStyle.HEADER;
populateUIWithQuotedMessage(message, true, Action.FORWARD);
populateUIWithQuotedMessage(messageViewInfo, true, Action.FORWARD);
}
public void initFromReplyToMessage(Message message, Action action)
public void initFromReplyToMessage(MessageViewInfo messageViewInfo, Action action)
throws MessagingException {
populateUIWithQuotedMessage(message, account.isDefaultQuotedTextShown(), action);
populateUIWithQuotedMessage(messageViewInfo, account.isDefaultQuotedTextShown(), action);
}
public void processDraftMessage(LocalMessage message, Map<IdentityField, String> k9identity)
public void processDraftMessage(MessageViewInfo messageViewInfo, Map<IdentityField, String> k9identity)
throws MessagingException {
quoteStyle = k9identity.get(IdentityField.QUOTE_STYLE) != null
? QuoteStyle.valueOf(k9identity.get(IdentityField.QUOTE_STYLE))
@ -255,7 +235,7 @@ public class QuotedMessagePresenter {
// composition window. If that's the case, try and convert it to text to
// match the behavior in text mode.
view.setMessageContentCharacters(
QuotedMessageHelper.getBodyTextFromMessage(message, SimpleMessageFormat.TEXT));
QuotedMessageHelper.getBodyTextFromMessage(messageViewInfo.message, SimpleMessageFormat.TEXT));
forcePlainText = true;
showOrHideQuotedText(quotedMode);
@ -263,7 +243,7 @@ public class QuotedMessagePresenter {
}
if (messageFormat == MessageFormat.HTML) {
Part part = MimeUtility.findFirstPartByMimeType(message, "text/html");
Part part = MimeUtility.findFirstPartByMimeType(messageViewInfo.message, "text/html");
if (part != null) { // Shouldn't happen if we were the one who saved it.
quotedTextFormat = SimpleMessageFormat.HTML;
String text = MessageExtractor.getTextFromPart(part);
@ -298,15 +278,15 @@ public class QuotedMessagePresenter {
}
// TODO replace with MessageViewInfo data
view.setQuotedHtml(quotedHtmlContent.getQuotedContent(),
AttachmentResolver.createFromPart(message));
AttachmentResolver.createFromPart(messageViewInfo.rootPart));
}
}
if (bodyPlainOffset != null && bodyPlainLength != null) {
processSourceMessageText(message, bodyPlainOffset, bodyPlainLength, false);
processSourceMessageText(messageViewInfo.rootPart, bodyPlainOffset, bodyPlainLength, false);
}
} else if (messageFormat == MessageFormat.TEXT) {
quotedTextFormat = SimpleMessageFormat.TEXT;
processSourceMessageText(message, bodyOffset, bodyLength, true);
processSourceMessageText(messageViewInfo.rootPart, bodyOffset, bodyLength, true);
} else {
Log.e(K9.LOG_TAG, "Unhandled message format.");
}
@ -324,14 +304,13 @@ public class QuotedMessagePresenter {
/**
* Pull out the parts of the now loaded source message and apply them to the new message
* depending on the type of message being composed.
* @param message Source message
* @param bodyOffset Insertion point for reply.
* @param bodyLength Length of reply.
* @param viewMessageContent Update mMessageContentView or not.
*/
private void processSourceMessageText(Message message, int bodyOffset, int bodyLength, boolean viewMessageContent)
private void processSourceMessageText(Part rootMessagePart, int bodyOffset, int bodyLength, boolean viewMessageContent)
throws MessagingException {
Part textPart = MimeUtility.findFirstPartByMimeType(message, "text/plain");
Part textPart = MimeUtility.findFirstPartByMimeType(rootMessagePart, "text/plain");
if (textPart == null) {
return;
}

View file

@ -13,6 +13,7 @@ import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
import android.util.Log;
@ -69,6 +70,7 @@ public class MessageCryptoHelper {
private MessageCryptoCallback callback;
private LocalMessage currentMessage;
private OpenPgpDecryptionResult cachedDecryptionResult;
private MessageCryptoAnnotations queuedResult;
private PendingIntent queuedPendingIntent;
@ -95,7 +97,8 @@ public class MessageCryptoHelper {
}
}
public void asyncStartOrResumeProcessingMessage(LocalMessage message, MessageCryptoCallback callback) {
public void asyncStartOrResumeProcessingMessage(LocalMessage message, MessageCryptoCallback callback,
OpenPgpDecryptionResult cachedDecryptionResult) {
if (this.currentMessage != null) {
reattachCallback(message, callback);
return;
@ -103,6 +106,7 @@ public class MessageCryptoHelper {
this.messageAnnotations = new MessageCryptoAnnotations();
this.currentMessage = message;
this.cachedDecryptionResult = cachedDecryptionResult;
this.callback = callback;
runFirstPass();
@ -229,18 +233,26 @@ public class MessageCryptoHelper {
Intent decryptIntent = userInteractionResultIntent;
userInteractionResultIntent = null;
if (decryptIntent == null) {
decryptIntent = new Intent();
decryptIntent = getDecryptionIntent();
}
decryptVerify(decryptIntent);
}
private void decryptVerify(Intent intent) {
intent.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
@NonNull
private Intent getDecryptionIntent() {
Intent decryptIntent = new Intent(OpenPgpApi.ACTION_DECRYPT_VERIFY);
Address[] from = currentMessage.getFrom();
if (from.length > 0) {
intent.putExtra(OpenPgpApi.EXTRA_SENDER_ADDRESS, from[0].getAddress());
decryptIntent.putExtra(OpenPgpApi.EXTRA_SENDER_ADDRESS, from[0].getAddress());
}
decryptIntent.putExtra(OpenPgpApi.EXTRA_DECRYPTION_RESULT, cachedDecryptionResult);
return decryptIntent;
}
private void decryptVerify(Intent intent) {
try {
CryptoPartType cryptoPartType = currentCryptoPart.type;
switch (cryptoPartType) {

View file

@ -4,6 +4,7 @@ package com.fsck.k9.ui.message;
import android.content.AsyncTaskLoader;
import android.content.Context;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
import android.util.Log;
import com.fsck.k9.K9;
@ -45,6 +46,7 @@ public class LocalMessageExtractorLoader extends AsyncTaskLoader<MessageViewInfo
}
@Override
@WorkerThread
public MessageViewInfo loadInBackground() {
try {
return MessageViewInfoExtractor.extractMessageForView(getContext(), message, annotations);

View file

@ -9,6 +9,7 @@ import android.content.IntentSender;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.util.Log;
@ -201,6 +202,13 @@ public class MessageCryptoPresenter implements OnCryptoClickListener {
messageCryptoMvpView.redisplayMessage();
}
public Parcelable getDecryptionResultForReply() {
if (cryptoResultAnnotation != null && cryptoResultAnnotation.isOpenPgpResult()) {
return cryptoResultAnnotation.getOpenPgpDecryptionResult();
}
return null;
}
@Nullable
private static Drawable getOpenPgpApiProviderIcon(Context context, Account account) {
try {

View file

@ -16,6 +16,7 @@ import android.content.IntentSender.SendIntentException;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.Log;
import android.view.ContextThemeWrapper;
@ -203,7 +204,7 @@ public class MessageViewFragment extends Fragment implements ConfirmationDialogF
}
mAccount = Preferences.getPreferences(getApplicationContext()).getAccount(mMessageReference.getAccountUuid());
messageLoaderHelper.asyncStartOrResumeLoadingMessage(messageReference);
messageLoaderHelper.asyncStartOrResumeLoadingMessage(messageReference, null);
mFragmentListener.updateMenu();
}
@ -319,19 +320,19 @@ public class MessageViewFragment extends Fragment implements ConfirmationDialogF
public void onReply() {
if (mMessage != null) {
mFragmentListener.onReply(mMessage);
mFragmentListener.onReply(mMessage, messageCryptoPresenter.getDecryptionResultForReply());
}
}
public void onReplyAll() {
if (mMessage != null) {
mFragmentListener.onReplyAll(mMessage);
mFragmentListener.onReplyAll(mMessage, messageCryptoPresenter.getDecryptionResultForReply());
}
}
public void onForward() {
if (mMessage != null) {
mFragmentListener.onForward(mMessage);
mFragmentListener.onForward(mMessage, messageCryptoPresenter.getDecryptionResultForReply());
}
}
@ -690,15 +691,15 @@ public class MessageViewFragment extends Fragment implements ConfirmationDialogF
}
public interface MessageViewFragmentListener {
public void onForward(LocalMessage mMessage);
public void disableDeleteAction();
public void onReplyAll(LocalMessage mMessage);
public void onReply(LocalMessage mMessage);
public void displayMessageSubject(String title);
public void setProgress(boolean b);
public void showNextMessageOrReturn();
public void messageHeaderViewAvailable(MessageHeader messageHeaderView);
public void updateMenu();
void onForward(LocalMessage mMessage, Parcelable decryptionResultForReply);
void disableDeleteAction();
void onReplyAll(LocalMessage mMessage, Parcelable decryptionResultForReply);
void onReply(LocalMessage mMessage, Parcelable decryptionResultForReply);
void displayMessageSubject(String title);
void setProgress(boolean b);
void showNextMessageOrReturn();
void messageHeaderViewAvailable(MessageHeader messageHeaderView);
void updateMenu();
}
public boolean isInitialized() {

View file

@ -25,7 +25,7 @@ public class OpenPgpDecryptionResult implements Parcelable {
* old versions of the protocol (and thus old versions of this class), we need a versioning
* system for the parcels sent between the clients and the providers.
*/
public static final int PARCELABLE_VERSION = 1;
public static final int PARCELABLE_VERSION = 2;
// content not encrypted
public static final int RESULT_NOT_ENCRYPTED = -1;
@ -34,26 +34,33 @@ public class OpenPgpDecryptionResult implements Parcelable {
// encrypted
public static final int RESULT_ENCRYPTED = 1;
int result;
public final int result;
public final byte[] sessionKey;
public final byte[] decryptedSessionKey;
public int getResult() {
return result;
}
public void setResult(int result) {
this.result = result;
}
public OpenPgpDecryptionResult() {
}
public OpenPgpDecryptionResult(int result) {
this.result = result;
this.sessionKey = null;
this.decryptedSessionKey = null;
}
public OpenPgpDecryptionResult(int result, byte[] sessionKey, byte[] decryptedSessionKey) {
this.result = result;
if ((sessionKey == null) != (decryptedSessionKey == null)) {
throw new AssertionError("sessionkey must be null iff decryptedSessionKey is null");
}
this.sessionKey = sessionKey;
this.decryptedSessionKey = decryptedSessionKey;
}
public OpenPgpDecryptionResult(OpenPgpDecryptionResult b) {
this.result = b.result;
this.sessionKey = b.sessionKey;
this.decryptedSessionKey = b.decryptedSessionKey;
}
public int describeContents() {
@ -73,6 +80,9 @@ public class OpenPgpDecryptionResult implements Parcelable {
int startPosition = dest.dataPosition();
// version 1
dest.writeInt(result);
// version 2
dest.writeByteArray(sessionKey);
dest.writeByteArray(decryptedSessionKey);
// Go back and write the size
int parcelableSize = dest.dataPosition() - startPosition;
dest.setDataPosition(sizePosition);
@ -82,12 +92,15 @@ public class OpenPgpDecryptionResult implements Parcelable {
public static final Creator<OpenPgpDecryptionResult> CREATOR = new Creator<OpenPgpDecryptionResult>() {
public OpenPgpDecryptionResult createFromParcel(final Parcel source) {
source.readInt(); // parcelableVersion
int version = source.readInt(); // parcelableVersion
int parcelableSize = source.readInt();
int startPosition = source.dataPosition();
OpenPgpDecryptionResult vr = new OpenPgpDecryptionResult();
vr.result = source.readInt();
int result = source.readInt();
byte[] sessionKey = version > 1 ? source.createByteArray() : null;
byte[] decryptedSessionKey = version > 1 ? source.createByteArray() : null;
OpenPgpDecryptionResult vr = new OpenPgpDecryptionResult(result, sessionKey, decryptedSessionKey);
// skip over all fields added in future versions of this parcel
source.setDataPosition(startPosition + parcelableSize);

View file

@ -258,6 +258,7 @@ public class OpenPgpApi {
public static final String RESULT_INTENT = "intent";
// DECRYPT_VERIFY
public static final String EXTRA_DECRYPTION_RESULT = "decryption_result";
public static final String EXTRA_DETACHED_SIGNATURE = "detached_signature";
public static final String EXTRA_PROGRESS_MESSENGER = "progress_messenger";
public static final String EXTRA_DATA_LENGTH = "data_length";