Merge pull request #1390

Fix reply to all, and some refactorings for initFromReplyTo
This commit is contained in:
cketti 2016-05-21 00:00:37 +02:00
commit 1eb0ef4a6b
12 changed files with 418 additions and 201 deletions

View file

@ -90,7 +90,7 @@ public abstract class Message implements Part, CompositeBody {
public abstract void setSentDate(Date sentDate, boolean hideTimeZone) throws MessagingException;
public abstract Address[] getRecipients(RecipientType type) throws MessagingException;
public abstract Address[] getRecipients(RecipientType type);
public abstract void setRecipients(RecipientType type, Address[] addresses)
throws MessagingException;

View file

@ -197,25 +197,29 @@ public class MimeMessage extends Message {
* found the method returns an empty array.
*/
@Override
public Address[] getRecipients(RecipientType type) throws MessagingException {
if (type == RecipientType.TO) {
if (mTo == null) {
mTo = Address.parse(MimeUtility.unfold(getFirstHeader("To")));
public Address[] getRecipients(RecipientType type) {
switch (type) {
case TO: {
if (mTo == null) {
mTo = Address.parse(MimeUtility.unfold(getFirstHeader("To")));
}
return mTo;
}
return mTo;
} else if (type == RecipientType.CC) {
if (mCc == null) {
mCc = Address.parse(MimeUtility.unfold(getFirstHeader("CC")));
case CC: {
if (mCc == null) {
mCc = Address.parse(MimeUtility.unfold(getFirstHeader("CC")));
}
return mCc;
}
return mCc;
} else if (type == RecipientType.BCC) {
if (mBcc == null) {
mBcc = Address.parse(MimeUtility.unfold(getFirstHeader("BCC")));
case BCC: {
if (mBcc == null) {
mBcc = Address.parse(MimeUtility.unfold(getFirstHeader("BCC")));
}
return mBcc;
}
return mBcc;
} else {
throw new MessagingException("Unrecognized recipient type.");
}
throw new IllegalArgumentException("Unrecognized recipient type.");
}
@Override

View file

@ -538,33 +538,24 @@ public class K9 extends Application {
MessagingController.getInstance(this).addListener(new MessagingListener() {
private void broadcastIntent(String action, Account account, String folder, Message message) {
try {
Uri uri = Uri.parse("email://messages/" + account.getAccountNumber() + "/" + Uri.encode(folder) + "/" + Uri.encode(message.getUid()));
Intent intent = new Intent(action, uri);
intent.putExtra(K9.Intents.EmailReceived.EXTRA_ACCOUNT, account.getDescription());
intent.putExtra(K9.Intents.EmailReceived.EXTRA_FOLDER, folder);
intent.putExtra(K9.Intents.EmailReceived.EXTRA_SENT_DATE, message.getSentDate());
intent.putExtra(K9.Intents.EmailReceived.EXTRA_FROM, Address.toString(message.getFrom()));
intent.putExtra(K9.Intents.EmailReceived.EXTRA_TO, Address.toString(message.getRecipients(Message.RecipientType.TO)));
intent.putExtra(K9.Intents.EmailReceived.EXTRA_CC, Address.toString(message.getRecipients(Message.RecipientType.CC)));
intent.putExtra(K9.Intents.EmailReceived.EXTRA_BCC, Address.toString(message.getRecipients(Message.RecipientType.BCC)));
intent.putExtra(K9.Intents.EmailReceived.EXTRA_SUBJECT, message.getSubject());
intent.putExtra(K9.Intents.EmailReceived.EXTRA_FROM_SELF, account.isAnIdentity(message.getFrom()));
K9.this.sendBroadcast(intent);
if (K9.DEBUG)
Log.d(K9.LOG_TAG, "Broadcasted: action=" + action
+ " account=" + account.getDescription()
+ " folder=" + folder
+ " message uid=" + message.getUid()
);
} catch (MessagingException e) {
Log.w(K9.LOG_TAG, "Error: action=" + action
Uri uri = Uri.parse("email://messages/" + account.getAccountNumber() + "/" + Uri.encode(folder) + "/" + Uri.encode(message.getUid()));
Intent intent = new Intent(action, uri);
intent.putExtra(K9.Intents.EmailReceived.EXTRA_ACCOUNT, account.getDescription());
intent.putExtra(K9.Intents.EmailReceived.EXTRA_FOLDER, folder);
intent.putExtra(K9.Intents.EmailReceived.EXTRA_SENT_DATE, message.getSentDate());
intent.putExtra(K9.Intents.EmailReceived.EXTRA_FROM, Address.toString(message.getFrom()));
intent.putExtra(K9.Intents.EmailReceived.EXTRA_TO, Address.toString(message.getRecipients(Message.RecipientType.TO)));
intent.putExtra(K9.Intents.EmailReceived.EXTRA_CC, Address.toString(message.getRecipients(Message.RecipientType.CC)));
intent.putExtra(K9.Intents.EmailReceived.EXTRA_BCC, Address.toString(message.getRecipients(Message.RecipientType.BCC)));
intent.putExtra(K9.Intents.EmailReceived.EXTRA_SUBJECT, message.getSubject());
intent.putExtra(K9.Intents.EmailReceived.EXTRA_FROM_SELF, account.isAnIdentity(message.getFrom()));
K9.this.sendBroadcast(intent);
if (K9.DEBUG)
Log.d(K9.LOG_TAG, "Broadcasted: action=" + action
+ " account=" + account.getDescription()
+ " folder=" + folder
+ " message uid=" + message.getUid()
);
}
}
private void updateUnreadWidget() {

View file

@ -76,6 +76,7 @@ import com.fsck.k9.fragment.ProgressDialogFragment.CancelListener;
import com.fsck.k9.helper.Contacts;
import com.fsck.k9.helper.IdentityHelper;
import com.fsck.k9.helper.MailTo;
import com.fsck.k9.helper.ReplyToParser;
import com.fsck.k9.helper.SimpleTextWatcher;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Flag;
@ -400,7 +401,8 @@ public class MessageCompose extends K9Activity implements OnClickListener,
RecipientMvpView recipientMvpView = new RecipientMvpView(this);
ComposePgpInlineDecider composePgpInlineDecider = new ComposePgpInlineDecider();
recipientPresenter = new RecipientPresenter(this, recipientMvpView, mAccount, composePgpInlineDecider);
recipientPresenter = new RecipientPresenter(this, recipientMvpView, mAccount,
composePgpInlineDecider, new ReplyToParser());
mSubjectView = (EditText) findViewById(R.id.subject);
mSubjectView.getInputExtras(true).putBoolean("allowEmoji", true);
@ -1598,7 +1600,8 @@ public class MessageCompose extends K9Activity implements OnClickListener,
* If a reply-to was included with the message use that, otherwise use the from
* or sender address.
*/
recipientPresenter.initFromReplyToMessage(message);
boolean isReplyAll = mAction == Action.REPLY_ALL;
recipientPresenter.initFromReplyToMessage(message, isReplyAll);
if (message.getMessageId() != null && message.getMessageId().length() > 0) {
mInReplyTo = message.getMessageId();

View file

@ -27,15 +27,14 @@ import com.fsck.k9.activity.compose.ComposeCryptoStatus.SendErrorState;
import com.fsck.k9.helper.Contacts;
import com.fsck.k9.helper.MailTo;
import com.fsck.k9.helper.ReplyToParser;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.helper.ReplyToParser.ReplyToAddresses;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mailstore.LocalMessage;
import com.fsck.k9.message.PgpMessageBuilder;
import com.fsck.k9.message.ComposePgpInlineDecider;
import com.fsck.k9.message.PgpMessageBuilder;
import com.fsck.k9.view.RecipientSelectView.Recipient;
import org.openintents.openpgp.IOpenPgpService2;
import org.openintents.openpgp.util.OpenPgpApi;
@ -61,6 +60,7 @@ public class RecipientPresenter implements PermissionPingCallback {
private final Context context;
private final RecipientMvpView recipientMvpView;
private final ComposePgpInlineDecider composePgpInlineDecider;
private ReplyToParser replyToParser;
private Account account;
private String cryptoProvider;
private Boolean hasContactPicker;
@ -78,10 +78,11 @@ public class RecipientPresenter implements PermissionPingCallback {
public RecipientPresenter(Context context, RecipientMvpView recipientMvpView, Account account,
ComposePgpInlineDecider composePgpInlineDecider) {
ComposePgpInlineDecider composePgpInlineDecider, ReplyToParser replyToParser) {
this.recipientMvpView = recipientMvpView;
this.context = context;
this.composePgpInlineDecider = composePgpInlineDecider;
this.replyToParser = replyToParser;
recipientMvpView.setPresenter(this);
onSwitchAccount(account);
@ -134,50 +135,17 @@ public class RecipientPresenter implements PermissionPingCallback {
return false;
}
public void initFromReplyToMessage(Message message) {
Address[] replyToAddresses = ReplyToParser.getRecipientsToReplyTo(message);
try {
// if we're replying to a message we sent, we probably meant
// to reply to the recipient of that message
if (account.isAnIdentity(replyToAddresses)) {
replyToAddresses = message.getRecipients(RecipientType.TO);
}
public void initFromReplyToMessage(Message message, boolean isReplyAll) {
ReplyToAddresses replyToAddresses = isReplyAll ?
replyToParser.getRecipientsToReplyAllTo(message, account) :
replyToParser.getRecipientsToReplyTo(message, account);
addRecipientsFromAddresses(RecipientType.TO, replyToAddresses);
addToAddresses(replyToAddresses.to);
addCcAddresses(replyToAddresses.cc);
if (message.getReplyTo().length > 0) {
for (Address address : message.getFrom()) {
if (!account.isAnIdentity(address) && !Utility.arrayContains(replyToAddresses, address)) {
addRecipientsFromAddresses(RecipientType.TO, address);
}
}
}
for (Address address : message.getRecipients(RecipientType.TO)) {
if (!account.isAnIdentity(address) && !Utility.arrayContains(replyToAddresses, address)) {
addToAddresses(address);
}
}
if (message.getRecipients(RecipientType.CC).length > 0) {
for (Address address : message.getRecipients(RecipientType.CC)) {
if (!account.isAnIdentity(address) && !Utility.arrayContains(replyToAddresses, address)) {
addCcAddresses(address);
}
}
}
boolean shouldSendAsPgpInline = composePgpInlineDecider.shouldReplyInline(message);
if (shouldSendAsPgpInline) {
cryptoEnablePgpInline = true;
}
} catch (MessagingException e) {
// can't happen, we know the recipient types exist
throw new AssertionError(e);
boolean shouldSendAsPgpInline = composePgpInlineDecider.shouldReplyInline(message);
if (shouldSendAsPgpInline) {
cryptoEnablePgpInline = true;
}
}
@ -228,18 +196,13 @@ public class RecipientPresenter implements PermissionPingCallback {
}
private void initRecipientsFromDraftMessage(LocalMessage message) {
try {
addToAddresses(message.getRecipients(RecipientType.TO));
addToAddresses(message.getRecipients(RecipientType.TO));
Address[] ccRecipients = message.getRecipients(RecipientType.CC);
addCcAddresses(ccRecipients);
Address[] ccRecipients = message.getRecipients(RecipientType.CC);
addCcAddresses(ccRecipients);
Address[] bccRecipients = message.getRecipients(RecipientType.BCC);
addBccAddresses(bccRecipients);
} catch (MessagingException e) {
// can't happen, we know the recipient types exist
throw new AssertionError(e);
}
Address[] bccRecipients = message.getRecipients(RecipientType.BCC);
addBccAddresses(bccRecipients);
}
private void initPgpInlineFromDraftMessage(LocalMessage message) {

View file

@ -1,13 +1,10 @@
package com.fsck.k9.helper;
import android.util.Log;
import com.fsck.k9.Account;
import com.fsck.k9.Identity;
import com.fsck.k9.K9;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
public class IdentityHelper {
@ -27,28 +24,24 @@ public class IdentityHelper {
public static Identity getRecipientIdentityFromMessage(Account account, Message message) {
Identity recipient = null;
try {
for (Address address : message.getRecipients(Message.RecipientType.TO)) {
Identity identity = account.findIdentity(address);
if (identity != null) {
recipient = identity;
break;
}
for (Address address : message.getRecipients(Message.RecipientType.TO)) {
Identity identity = account.findIdentity(address);
if (identity != null) {
recipient = identity;
break;
}
if (recipient == null) {
Address[] ccAddresses = message.getRecipients(Message.RecipientType.CC);
if (ccAddresses.length > 0) {
for (Address address : ccAddresses) {
Identity identity = account.findIdentity(address);
if (identity != null) {
recipient = identity;
break;
}
}
if (recipient == null) {
Address[] ccAddresses = message.getRecipients(Message.RecipientType.CC);
if (ccAddresses.length > 0) {
for (Address address : ccAddresses) {
Identity identity = account.findIdentity(address);
if (identity != null) {
recipient = identity;
break;
}
}
}
} catch (MessagingException e) {
Log.w(K9.LOG_TAG, "Error finding the identity this message was sent to", e);
}
if (recipient == null) {

View file

@ -1,12 +1,12 @@
package com.fsck.k9.helper;
import android.content.Context;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.util.Log;
import com.fsck.k9.Account;
import com.fsck.k9.K9;
@ -15,7 +15,6 @@ import com.fsck.k9.activity.FolderInfoHolder;
import com.fsck.k9.activity.MessageInfoHolder;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.mailstore.LocalMessage;
@ -53,45 +52,42 @@ public class MessageHelper {
final FolderInfoHolder folder,
Account account) {
final Contacts contactHelper = K9.showContactName() ? Contacts.getInstance(mContext) : null;
try {
target.message = message;
target.compareArrival = message.getInternalDate();
target.compareDate = message.getSentDate();
if (target.compareDate == null) {
target.compareDate = message.getInternalDate();
}
target.folder = folder;
target.read = message.isSet(Flag.SEEN);
target.answered = message.isSet(Flag.ANSWERED);
target.forwarded = message.isSet(Flag.FORWARDED);
target.flagged = message.isSet(Flag.FLAGGED);
Address[] addrs = message.getFrom();
if (addrs.length > 0 && account.isAnIdentity(addrs[0])) {
CharSequence to = toFriendly(message.getRecipients(RecipientType.TO), contactHelper);
target.compareCounterparty = to.toString();
target.sender = new SpannableStringBuilder(mContext.getString(R.string.message_to_label)).append(to);
} else {
target.sender = toFriendly(addrs, contactHelper);
target.compareCounterparty = target.sender.toString();
}
if (addrs.length > 0) {
target.senderAddress = addrs[0].getAddress();
} else {
// a reasonable fallback "whomever we were corresponding with
target.senderAddress = target.compareCounterparty;
}
target.uid = message.getUid();
target.account = message.getFolder().getAccountUuid();
target.uri = message.getUri();
} catch (MessagingException me) {
Log.w(K9.LOG_TAG, "Unable to load message info", me);
target.message = message;
target.compareArrival = message.getInternalDate();
target.compareDate = message.getSentDate();
if (target.compareDate == null) {
target.compareDate = message.getInternalDate();
}
target.folder = folder;
target.read = message.isSet(Flag.SEEN);
target.answered = message.isSet(Flag.ANSWERED);
target.forwarded = message.isSet(Flag.FORWARDED);
target.flagged = message.isSet(Flag.FLAGGED);
Address[] addrs = message.getFrom();
if (addrs.length > 0 && account.isAnIdentity(addrs[0])) {
CharSequence to = toFriendly(message.getRecipients(RecipientType.TO), contactHelper);
target.compareCounterparty = to.toString();
target.sender = new SpannableStringBuilder(mContext.getString(R.string.message_to_label)).append(to);
} else {
target.sender = toFriendly(addrs, contactHelper);
target.compareCounterparty = target.sender.toString();
}
if (addrs.length > 0) {
target.senderAddress = addrs[0].getAddress();
} else {
// a reasonable fallback "whomever we were corresponding with
target.senderAddress = target.compareCounterparty;
}
target.uid = message.getUid();
target.account = message.getFolder().getAccountUuid();
target.uri = message.getUri();
}
public CharSequence getDisplayName(Account account, Address[] fromAddrs, Address[] toAddrs) {

View file

@ -1,23 +1,91 @@
package com.fsck.k9.helper;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import android.support.annotation.VisibleForTesting;
import com.fsck.k9.Account;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.mail.internet.ListHeaders;
public class ReplyToParser {
public static Address[] getRecipientsToReplyTo(Message message) {
public ReplyToAddresses getRecipientsToReplyTo(Message message, Account account) {
Address[] candidateAddress;
Address[] replyToAddresses = message.getReplyTo();
if (replyToAddresses.length > 0) {
return replyToAddresses;
}
Address[] listPostAddresses = ListHeaders.getListPostAddresses(message);
if (listPostAddresses.length > 0) {
return listPostAddresses;
Address[] fromAddresses = message.getFrom();
if (replyToAddresses.length > 0) {
candidateAddress = replyToAddresses;
} else if (listPostAddresses.length > 0) {
candidateAddress = listPostAddresses;
} else {
candidateAddress = fromAddresses;
}
return message.getFrom();
boolean replyToAddressIsUserIdentity = account.isAnIdentity(candidateAddress);
if (replyToAddressIsUserIdentity) {
candidateAddress = message.getRecipients(RecipientType.TO);
}
return new ReplyToAddresses(candidateAddress);
}
public ReplyToAddresses getRecipientsToReplyAllTo(Message message, Account account) {
List<Address> replyToAddresses = Arrays.asList(getRecipientsToReplyTo(message, account).to);
HashSet<Address> alreadyAddedAddresses = new HashSet<>(replyToAddresses);
ArrayList<Address> toAddresses = new ArrayList<>(replyToAddresses);
ArrayList<Address> ccAddresses = new ArrayList<>();
for (Address address : message.getFrom()) {
if (!alreadyAddedAddresses.contains(address) && !account.isAnIdentity(address)) {
toAddresses.add(address);
alreadyAddedAddresses.add(address);
}
}
for (Address address : message.getRecipients(RecipientType.TO)) {
if (!alreadyAddedAddresses.contains(address) && !account.isAnIdentity(address)) {
toAddresses.add(address);
alreadyAddedAddresses.add(address);
}
}
for (Address address : message.getRecipients(RecipientType.CC)) {
if (!alreadyAddedAddresses.contains(address) && !account.isAnIdentity(address)) {
ccAddresses.add(address);
alreadyAddedAddresses.add(address);
}
}
return new ReplyToAddresses(toAddresses, ccAddresses);
}
public static class ReplyToAddresses {
public final Address[] to;
public final Address[] cc;
@VisibleForTesting
public ReplyToAddresses(List<Address> toAddresses, List<Address> ccAddresses) {
to = toAddresses.toArray(new Address[toAddresses.size()]);
cc = ccAddresses.toArray(new Address[ccAddresses.size()]);
}
@VisibleForTesting
public ReplyToAddresses(Address[] toAddresses) {
to = toAddresses;
cc = new Address[0];
}
}
}

View file

@ -106,29 +106,25 @@ class NotificationContentCreator {
}
private String getMessageSender(Account account, Message message) {
try {
boolean isSelf = false;
final Contacts contacts = K9.showContactName() ? Contacts.getInstance(context) : null;
final Address[] fromAddresses = message.getFrom();
boolean isSelf = false;
final Contacts contacts = K9.showContactName() ? Contacts.getInstance(context) : null;
final Address[] fromAddresses = message.getFrom();
if (fromAddresses != null) {
isSelf = account.isAnIdentity(fromAddresses);
if (!isSelf && fromAddresses.length > 0) {
return MessageHelper.toFriendly(fromAddresses[0], contacts).toString();
}
if (fromAddresses != null) {
isSelf = account.isAnIdentity(fromAddresses);
if (!isSelf && fromAddresses.length > 0) {
return MessageHelper.toFriendly(fromAddresses[0], contacts).toString();
}
}
if (isSelf) {
// show To: if the message was sent from me
Address[] recipients = message.getRecipients(Message.RecipientType.TO);
if (isSelf) {
// show To: if the message was sent from me
Address[] recipients = message.getRecipients(Message.RecipientType.TO);
if (recipients != null && recipients.length > 0) {
return context.getString(R.string.message_to_fmt,
MessageHelper.toFriendly(recipients[0], contacts).toString());
}
if (recipients != null && recipients.length > 0) {
return context.getString(R.string.message_to_fmt,
MessageHelper.toFriendly(recipients[0], contacts).toString());
}
} catch (MessagingException e) {
Log.e(K9.LOG_TAG, "Unable to get sender information for notification.", e);
}
return null;

View file

@ -193,11 +193,7 @@ public class MessageHeader extends LinearLayout implements OnClickListener, OnLo
}
private void onAddRecipientsToClipboard(Message.RecipientType recipientType) {
try {
onAddAddressesToClipboard(mMessage.getRecipients(recipientType));
} catch (MessagingException e) {
Log.e(K9.LOG_TAG, "Couldn't get recipients address", e);
}
onAddAddressesToClipboard(mMessage.getRecipients(recipientType));
}
public void setOnFlagListener(OnClickListener listener) {

View file

@ -0,0 +1,91 @@
package com.fsck.k9.activity.compose;
import java.util.Arrays;
import java.util.List;
import android.content.Context;
import com.fsck.k9.Account;
import com.fsck.k9.helper.ReplyToParser;
import com.fsck.k9.helper.ReplyToParser.ReplyToAddresses;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.message.ComposePgpInlineDecider;
import com.fsck.k9.view.RecipientSelectView.Recipient;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowApplication;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = "src/main/AndroidManifest.xml", sdk = 21)
public class RecipientPresenterTest {
public static final ReplyToAddresses TO_ADDRESSES = new ReplyToAddresses(Address.parse("to@example.org"));
public static final List<Address> ALL_TO_ADDRESSES = Arrays.asList(Address.parse("allTo@example.org"));
public static final List<Address> ALL_CC_ADDRESSES = Arrays.asList(Address.parse("allCc@example.org"));
RecipientPresenter recipientPresenter;
private ReplyToParser replyToParser;
private ComposePgpInlineDecider composePgpInlineDecider;
private Account account;
private RecipientMvpView recipientMvpView;
@Before
public void setUp() throws Exception {
Context context = ShadowApplication.getInstance().getApplicationContext();
recipientMvpView = mock(RecipientMvpView.class);
account = mock(Account.class);
composePgpInlineDecider = mock(ComposePgpInlineDecider.class);
replyToParser = mock(ReplyToParser.class);
recipientPresenter = new RecipientPresenter(
context, recipientMvpView, account, composePgpInlineDecider, replyToParser);
}
@Test
public void testInitFromReplyToMessage() throws Exception {
Message message = mock(Message.class);
when(replyToParser.getRecipientsToReplyTo(message, account)).thenReturn(TO_ADDRESSES);
recipientPresenter.initFromReplyToMessage(message, false);
verify(recipientMvpView).addRecipients(eq(RecipientType.TO), any(Recipient[].class));
}
@Test
public void testInitFromReplyToAllMessage() throws Exception {
Message message = mock(Message.class);
when(replyToParser.getRecipientsToReplyTo(message, account)).thenReturn(TO_ADDRESSES);
ReplyToAddresses replyToAddresses = new ReplyToAddresses(ALL_TO_ADDRESSES, ALL_CC_ADDRESSES);
when(replyToParser.getRecipientsToReplyAllTo(message, account)).thenReturn(replyToAddresses);
recipientPresenter.initFromReplyToMessage(message, true);
verify(recipientMvpView).addRecipients(eq(RecipientType.TO), any(Recipient.class));
verify(recipientMvpView).addRecipients(eq(RecipientType.CC), any(Recipient.class));
}
@Test
public void initFromReplyToMessage_shouldCallComposePgpInlineDecider() throws Exception {
Message message = mock(Message.class);
when(replyToParser.getRecipientsToReplyTo(message, account)).thenReturn(TO_ADDRESSES);
recipientPresenter.initFromReplyToMessage(message, false);
verify(composePgpInlineDecider).shouldReplyInline(message);
}
}

View file

@ -1,8 +1,14 @@
package com.fsck.k9.helper;
import java.lang.reflect.Array;
import java.util.ArrayList;
import com.fsck.k9.Account;
import com.fsck.k9.helper.ReplyToParser.ReplyToAddresses;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.mail.internet.ListHeaders;
import org.junit.Before;
import org.junit.Test;
@ -11,25 +17,38 @@ import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE, sdk = 21)
public class ReplyToParserTest {
private static final Address[] REPLY_TO_ADDRESSES = createAddressArray("replyTo@example.com");
private static final Address[] LIST_POST_ADDRESSES = createAddressArray("listPost@example.com");
private static final Address[] FROM_ADDRESSES = createAddressArray("from@example.com");
private static final Address[] REPLY_TO_ADDRESSES = Address.parse("replyTo1@example.com, replyTo2@example.com");
private static final Address[] LIST_POST_ADDRESSES = Address.parse("listPost@example.com");
private static final Address[] FROM_ADDRESSES = Address.parse("from@example.com");
private static final Address[] TO_ADDRESSES = Address.parse("to1@example.com, to2@example.com");
private static final Address[] CC_ADDRESSES = Address.parse("cc1@example.com, cc2@example.com");
private static final String[] LIST_POST_HEADER_VALUES = new String[] { "<mailto:listPost@example.com>" };
public static final Address[] EMPTY_ADDRESSES = new Address[0];
private ReplyToParser replyToParser;
private Message message;
private Account account;
@Before
public void setUp() throws Exception {
message = mock(Message.class);
account = mock(Account.class);
replyToParser = new ReplyToParser();
}
@Test
@ -38,34 +57,131 @@ public class ReplyToParserTest {
when(message.getHeader(ListHeaders.LIST_POST_HEADER)).thenReturn(LIST_POST_HEADER_VALUES);
when(message.getFrom()).thenReturn(FROM_ADDRESSES);
Address[] result = ReplyToParser.getRecipientsToReplyTo(message);
ReplyToAddresses result = replyToParser.getRecipientsToReplyTo(message, account);
assertArrayEquals(REPLY_TO_ADDRESSES, result);
assertArrayEquals(REPLY_TO_ADDRESSES, result.to);
assertArrayEquals(EMPTY_ADDRESSES, result.cc);
verify(account).isAnIdentity(result.to);
}
@Test
public void getRecipientsToReplyTo_should_prefer_from_ifOtherIsIdentity() throws Exception {
when(message.getReplyTo()).thenReturn(REPLY_TO_ADDRESSES);
when(message.getHeader(ListHeaders.LIST_POST_HEADER)).thenReturn(LIST_POST_HEADER_VALUES);
when(message.getFrom()).thenReturn(FROM_ADDRESSES);
when(message.getRecipients(RecipientType.TO)).thenReturn(TO_ADDRESSES);
when(account.isAnIdentity(any(Address[].class))).thenReturn(true);
ReplyToAddresses result = replyToParser.getRecipientsToReplyTo(message, account);
assertArrayEquals(TO_ADDRESSES, result.to);
assertArrayEquals(EMPTY_ADDRESSES, result.cc);
}
@Test
public void getRecipientsToReplyTo_should_prefer_listPost_over_from_field() throws Exception {
when(message.getReplyTo()).thenReturn(new Address[0]);
when(message.getReplyTo()).thenReturn(EMPTY_ADDRESSES);
when(message.getHeader(ListHeaders.LIST_POST_HEADER)).thenReturn(LIST_POST_HEADER_VALUES);
when(message.getFrom()).thenReturn(FROM_ADDRESSES);
Address[] result = ReplyToParser.getRecipientsToReplyTo(message);
ReplyToAddresses result = replyToParser.getRecipientsToReplyTo(message, account);
assertArrayEquals(LIST_POST_ADDRESSES, result);
assertArrayEquals(LIST_POST_ADDRESSES, result.to);
assertArrayEquals(EMPTY_ADDRESSES, result.cc);
verify(account).isAnIdentity(result.to);
}
@Test
public void getRecipientsToReplyTo_should_return_from_otherwise() throws Exception {
when(message.getReplyTo()).thenReturn(new Address[0]);
when(message.getReplyTo()).thenReturn(EMPTY_ADDRESSES);
when(message.getHeader(ListHeaders.LIST_POST_HEADER)).thenReturn(new String[0]);
when(message.getFrom()).thenReturn(FROM_ADDRESSES);
Address[] result = ReplyToParser.getRecipientsToReplyTo(message);
ReplyToAddresses result = replyToParser.getRecipientsToReplyTo(message, account);
assertArrayEquals(FROM_ADDRESSES, result);
assertArrayEquals(FROM_ADDRESSES, result.to);
assertArrayEquals(EMPTY_ADDRESSES, result.cc);
verify(account).isAnIdentity(result.to);
}
private static Address[] createAddressArray(String emailAddress) {
return new Address[] { new Address(emailAddress) };
@Test
public void getRecipientsToReplyAllTo_should_returnFromAndToAndCcRecipients() throws Exception {
when(message.getReplyTo()).thenReturn(EMPTY_ADDRESSES);
when(message.getHeader(ListHeaders.LIST_POST_HEADER)).thenReturn(new String[0]);
when(message.getFrom()).thenReturn(FROM_ADDRESSES);
when(message.getRecipients(RecipientType.TO)).thenReturn(TO_ADDRESSES);
when(message.getRecipients(RecipientType.CC)).thenReturn(CC_ADDRESSES);
ReplyToAddresses recipientsToReplyAllTo = replyToParser.getRecipientsToReplyAllTo(message, account);
assertArrayEquals(arrayConcatenate(FROM_ADDRESSES, TO_ADDRESSES, Address.class), recipientsToReplyAllTo.to);
assertArrayEquals(CC_ADDRESSES, recipientsToReplyAllTo.cc);
}
@Test
public void getRecipientsToReplyAllTo_should_excludeIdentityAddresses() throws Exception {
when(message.getReplyTo()).thenReturn(EMPTY_ADDRESSES);
when(message.getHeader(ListHeaders.LIST_POST_HEADER)).thenReturn(new String[0]);
when(message.getFrom()).thenReturn(EMPTY_ADDRESSES);
when(message.getRecipients(RecipientType.TO)).thenReturn(TO_ADDRESSES);
when(message.getRecipients(RecipientType.CC)).thenReturn(CC_ADDRESSES);
Address excludedCcAddress = CC_ADDRESSES[1];
Address excludedToAddress = TO_ADDRESSES[0];
when(account.isAnIdentity(eq(excludedToAddress))).thenReturn(true);
when(account.isAnIdentity(eq(excludedCcAddress))).thenReturn(true);
ReplyToAddresses recipientsToReplyAllTo = replyToParser.getRecipientsToReplyAllTo(message, account);
assertArrayEquals(arrayExcept(TO_ADDRESSES, excludedToAddress), recipientsToReplyAllTo.to);
assertArrayEquals(arrayExcept(CC_ADDRESSES, excludedCcAddress), recipientsToReplyAllTo.cc);
}
@Test
public void getRecipientsToReplyAllTo_should_excludeDuplicates() throws Exception {
when(message.getReplyTo()).thenReturn(REPLY_TO_ADDRESSES);
when(message.getFrom()).thenReturn(arrayConcatenate(FROM_ADDRESSES, REPLY_TO_ADDRESSES, Address.class));
when(message.getRecipients(RecipientType.TO)).thenReturn(arrayConcatenate(FROM_ADDRESSES, TO_ADDRESSES, Address.class));
when(message.getRecipients(RecipientType.CC)).thenReturn(arrayConcatenate(CC_ADDRESSES, TO_ADDRESSES, Address.class));
when(message.getHeader(ListHeaders.LIST_POST_HEADER)).thenReturn(new String[0]);
ReplyToAddresses recipientsToReplyAllTo = replyToParser.getRecipientsToReplyAllTo(message, account);
assertArrayContainsAll(REPLY_TO_ADDRESSES, recipientsToReplyAllTo.to);
assertArrayContainsAll(FROM_ADDRESSES, recipientsToReplyAllTo.to);
assertArrayContainsAll(TO_ADDRESSES, recipientsToReplyAllTo.to);
int totalExpectedAddresses = REPLY_TO_ADDRESSES.length + FROM_ADDRESSES.length + TO_ADDRESSES.length;
assertEquals(totalExpectedAddresses, recipientsToReplyAllTo.to.length);
assertArrayEquals(CC_ADDRESSES, recipientsToReplyAllTo.cc);
}
public <T> void assertArrayContainsAll(T[] expecteds, T[] actual) {
for (T expected : expecteds) {
assertTrue("Element must be in array (" + expected + ")", Utility.arrayContains(actual, expected));
}
}
public <T> T[] arrayConcatenate(T[] first, T[] second, Class<T> cls) {
// noinspection unchecked
T[] result = (T[]) Array.newInstance(cls, first.length + second.length);
System.arraycopy(first, 0, result, 0, first.length);
System.arraycopy(second, 0, result, first.length, second.length);
return result;
}
public <T> T[] arrayExcept(T[] in, T except) {
ArrayList<T> result = new ArrayList<>();
for (T element : in) {
if (!element.equals(except)) {
result.add(element);
}
}
// noinspection unchecked, it's a hack but it works
return result.toArray((T[]) new Object[result.size()]);
}
}