Fix reply all behavior to prevent duplicates, etc.

* Also create a bogus account if none exists to prevent setup failure
* Fixes #2087413 (Email large tests failing with Unknown URI)
* Fixes #2097361 (Duplicate email address when replying...)

Change-Id: I9ca2ac5f98db43e9009c22421c69956440356e34
This commit is contained in:
Marc Blank 2009-09-03 16:37:41 -07:00
parent 5d09beeacb
commit b8d0c55a05
2 changed files with 357 additions and 158 deletions

View File

@ -18,10 +18,8 @@ package com.android.email.activity;
import com.android.email.Controller;
import com.android.email.Email;
import com.android.email.EmailAddressAdapter;
import com.android.email.EmailAddressValidator;
import com.android.email.R;
import com.android.email.Utility;
import com.android.email.mail.Address;
import com.android.email.mail.MessagingException;
import com.android.email.mail.internet.EmailHtmlUtil;
@ -69,7 +67,6 @@ import android.widget.MultiAutoCompleteTextView;
import android.widget.TextView;
import android.widget.Toast;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
@ -432,6 +429,7 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
if (!dotFound) {
return null;
}
// we have found a comma-insert case. now just do it
// in the least expensive way we can.
if (source instanceof Spanned) {
@ -472,10 +470,10 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
mQuotedTextDelete.setOnClickListener(this);
EmailAddressAdapter addressAdapter = new EmailAddressAdapter(this);
// Temporarily disable addressAdapter, see BUG 2077496
// EmailAddressAdapter addressAdapter = new EmailAddressAdapter(this);
EmailAddressValidator addressValidator = new EmailAddressValidator();
// temporarilly disable setAdapter, see BUG 2077496
// mToView.setAdapter(addressAdapter);
mToView.setTokenizer(new Rfc822Tokenizer());
mToView.setValidator(addressValidator);
@ -1130,6 +1128,62 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
}
}
/**
* Given a packed address String, the address of our sending account, a view, and a list of
* addressees already added to other addressing views, adds unique addressees that don't
* match our address to the passed in view
*/
private boolean safeAddAddresses(String addrs, String ourAddress,
MultiAutoCompleteTextView view, ArrayList<Address> addrList) {
boolean added = false;
for (Address address : Address.unpack(addrs)) {
// Don't send to ourselves or already-included addresses
if (!address.getAddress().equalsIgnoreCase(ourAddress) && !addrList.contains(address)) {
addrList.add(address);
addAddress(view, address.toString());
added = true;
}
}
return added;
}
/**
* Set up the to and cc views properly for the "reply" and "replyAll" cases. What's important
* is that we not 1) send to ourselves, and 2) duplicate addressees.
* @param message the message we're replying to
* @param account the account we're sending from
* @param toView the "To" view
* @param ccView the "Cc" view
* @param replyAll whether this is a replyAll (vs a reply)
*/
/*package*/ void setupAddressViews(Message message, Account account,
MultiAutoCompleteTextView toView, MultiAutoCompleteTextView ccView, boolean replyAll) {
/*
* If a reply-to was included with the message use that, otherwise use the from
* or sender address.
*/
Address[] replyToAddresses = Address.unpack(message.mReplyTo);
if (replyToAddresses.length == 0) {
replyToAddresses = Address.unpack(message.mFrom);
}
addAddresses(mToView, replyToAddresses);
if (replyAll) {
// Keep a running list of addresses we're sending to
ArrayList<Address> allAddresses = new ArrayList<Address>();
String ourAddress = account.mEmailAddress;
for (Address address: replyToAddresses) {
allAddresses.add(address);
}
safeAddAddresses(message.mTo, ourAddress, mToView, allAddresses);
if (safeAddAddresses(message.mCc, ourAddress, mCcView, allAddresses)) {
mCcView.setVisibility(View.VISIBLE);
}
}
}
/**
* 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.
@ -1141,39 +1195,14 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
mDraftNeedsSaving = true;
final String subject = message.mSubject;
if (ACTION_REPLY.equals(action) || ACTION_REPLY_ALL.equals(action)) {
setupAddressViews(message, account, mToView, mCcView, ACTION_REPLY_ALL.equals(action));
if (subject != null && !subject.toLowerCase().startsWith("re:")) {
mSubjectView.setText("Re: " + subject);
} else {
mSubjectView.setText(subject);
}
/*
* If a reply-to was included with the message use that, otherwise use the from
* or sender address.
*/
Address[] replyToAddresses = Address.unpack(message.mReplyTo);
if (replyToAddresses.length == 0) {
replyToAddresses = Address.unpack(message.mFrom);
}
addAddresses(mToView, replyToAddresses);
if (ACTION_REPLY_ALL.equals(action)) {
for (Address address : Address.unpack(message.mTo)) {
if (!address.getAddress().equalsIgnoreCase(account.mEmailAddress)) {
addAddress(mToView, address.toString());
}
}
boolean makeCCVisible = false;
for (Address address : Address.unpack(message.mCc)) {
if (!Utility.arrayContains(replyToAddresses, address)) {
addAddress(mCcView, address.toString());
makeCCVisible = true;
}
}
if (makeCCVisible) {
mCcView.setVisibility(View.VISIBLE);
}
}
displayQuotedText(message);
} else if (ACTION_FORWARD.equals(action)) {
mSubjectView.setText(subject != null && !subject.toLowerCase().startsWith("fwd:") ?

View File

@ -17,26 +17,23 @@
package com.android.email.activity;
import com.android.email.Email;
import com.android.email.R;
import com.android.email.EmailAddressValidator;
import com.android.email.R;
import com.android.email.mail.Address;
import com.android.email.mail.MessagingException;
import com.android.email.mail.Message.RecipientType;
import com.android.email.mail.internet.MimeMessage;
import com.android.email.mail.internet.TextBody;
import com.android.email.provider.EmailContent;
import com.android.email.provider.EmailContent.Account;
import com.android.email.provider.EmailContent.Message;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.UiThreadTest;
import android.test.suitebuilder.annotation.LargeTest;
import android.view.View;
import android.widget.EditText;
import android.widget.AutoCompleteTextView;
import android.widget.MultiAutoCompleteTextView;
/**
@ -48,9 +45,11 @@ import android.widget.AutoCompleteTextView;
public class MessageComposeInstrumentationTests
extends ActivityInstrumentationTestCase2<MessageCompose> {
private AutoCompleteTextView mToView;
private MultiAutoCompleteTextView mToView;
private MultiAutoCompleteTextView mCcView;
private EditText mSubjectView;
private EditText mMessageView;
private long mCreatedAccountId = -1;
private static final String SENDER = "sender@android.com";
private static final String REPLYTO = "replyto@android.com";
@ -62,6 +61,15 @@ public class MessageComposeInstrumentationTests
private static final String REPLY_BODY_SHORT = "\n\n" + SENDER + " wrote:\n\n";
private static final String REPLY_BODY = REPLY_BODY_SHORT + ">" + BODY;
private static final String FROM = "Fred From <from@google.com>";
private static final String TO1 = "First To <first.to@google.com>";
private static final String TO2 = "Second To <second.to@google.com>";
private static final String TO3 = "CopyFirst Cc <first.cc@google.com>";
private static final String CC1 = "First Cc <first.cc@google.com>";
private static final String CC2 = "Second Cc <second.cc@google.com>";
private static final String CC3 = "Third Cc <third.cc@google.com>";
private static final String CC4 = "CopySecond To <second.to@google.com>";
private static final String UTF16_SENDER =
"\u3042\u3044\u3046 \u3048\u304A <sender@android.com>";
private static final String UTF16_REPLYTO =
@ -110,17 +118,37 @@ public class MessageComposeInstrumentationTests
// Force assignment of a default account
long accountId = Account.getDefaultAccountId(context);
if (accountId == -1) {
Account account = new Account();
account.mSenderName = "Bob Sender";
account.mEmailAddress = "bob@sender.com";
account.save(context);
accountId = account.mId;
mCreatedAccountId = accountId;
}
Account.restoreAccountWithId(context, accountId);
Email.setServicesEnabled(context);
Intent intent = new Intent(Intent.ACTION_VIEW);
setActivityIntent(intent);
final MessageCompose a = getActivity();
mToView = (AutoCompleteTextView) a.findViewById(R.id.to);
mToView = (MultiAutoCompleteTextView) a.findViewById(R.id.to);
mCcView = (MultiAutoCompleteTextView) a.findViewById(R.id.cc);
mSubjectView = (EditText) a.findViewById(R.id.subject);
mMessageView = (EditText) a.findViewById(R.id.message_content);
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
Context context = getInstrumentation().getTargetContext();
// If we created an account, delete it here
if (mCreatedAccountId > -1) {
context.getContentResolver().delete(
ContentUris.withAppendedId(Account.CONTENT_URI, mCreatedAccountId), null, null);
}
}
/**
* The name 'test preconditions' is a convention to signal that if this
* test doesn't pass, the test case was not set up properly and it might
@ -396,6 +424,148 @@ public class MessageComposeInstrumentationTests
}
/**
* Check that we create the proper to and cc addressees in reply and reply-all, making sure
* to reject duplicate addressees AND the email address of the sending account
*
* In this case, we're doing a "reply"
* The user is TO1 (a "to" recipient)
* The to should be: FROM
* The cc should be empty
*/
public void testReplyAddresses() throws Throwable {
final MessageCompose a = getActivity();
// Doesn't matter what Intent we use here
final Intent intent = new Intent(Intent.ACTION_VIEW);
Message msg = new Message();
final Account account = new Account();
msg.mFrom = Address.parseAndPack(FROM);
msg.mTo = Address.parseAndPack(TO1 + ',' + TO2);
msg.mCc = Address.parseAndPack(CC1 + ',' + CC2 + ',' + CC3);
final Message message = msg;
account.mEmailAddress = "FiRsT.tO@gOoGlE.cOm";
runTestOnUiThread(new Runnable() {
public void run() {
a.initFromIntent(intent);
a.setupAddressViews(message, account, mToView, mCcView, false);
assertEquals("", mCcView.getText().toString());
String result = Address.parseAndPack(mToView.getText().toString());
String expected = Address.parseAndPack(FROM);
assertEquals(expected, result);
}
});
}
/**
* Check that we create the proper to and cc addressees in reply and reply-all, making sure
* to reject duplicate addressees AND the email address of the sending account
*
* In this case, we're doing a "reply all"
* The user is TO1 (a "to" recipient)
* The to should be: FROM and TO2
* The cc should be: CC1, CC2, and CC3
*/
public void testReplyAllAddresses1() throws Throwable {
final MessageCompose a = getActivity();
// Doesn't matter what Intent we use here
final Intent intent = new Intent(Intent.ACTION_VIEW);
Message msg = new Message();
final Account account = new Account();
msg.mFrom = Address.parseAndPack(FROM);
msg.mTo = Address.parseAndPack(TO1 + ',' + TO2);
msg.mCc = Address.parseAndPack(CC1 + ',' + CC2 + ',' + CC3);
final Message message = msg;
account.mEmailAddress = "FiRsT.tO@gOoGlE.cOm";
runTestOnUiThread(new Runnable() {
public void run() {
a.initFromIntent(intent);
a.setupAddressViews(message, account, mToView, mCcView, true);
String result = Address.parseAndPack(mToView.getText().toString());
String expected = Address.parseAndPack(FROM + ',' + TO2);
assertEquals(expected, result);
result = Address.parseAndPack(mCcView.getText().toString());
expected = Address.parseAndPack(CC1 + ',' + CC2 + ',' + CC3);
assertEquals(expected, result);
}
});
}
/**
* Check that we create the proper to and cc addressees in reply and reply-all, making sure
* to reject duplicate addressees AND the email address of the sending account
*
* In this case, we're doing a "reply all"
* The user is CC2 (a "cc" recipient)
* The to should be: FROM, TO1, and TO2
* The cc should be: CC1 and CC3 (CC2 is our account's email address)
*/
public void testReplyAllAddresses2() throws Throwable {
final MessageCompose a = getActivity();
// Doesn't matter what Intent we use here
final Intent intent = new Intent(Intent.ACTION_VIEW);
Message msg = new Message();
final Account account = new Account();
msg.mFrom = Address.parseAndPack(FROM);
msg.mTo = Address.parseAndPack(TO1 + ',' + TO2);
msg.mCc = Address.parseAndPack(CC1 + ',' + CC2 + ',' + CC3);
final Message message = msg;
account.mEmailAddress = "sEcOnD.cC@gOoGlE.cOm";
runTestOnUiThread(new Runnable() {
public void run() {
a.initFromIntent(intent);
a.setupAddressViews(message, account, mToView, mCcView, true);
String result = Address.parseAndPack(mToView.getText().toString());
String expected = Address.parseAndPack(FROM + ',' + TO1 + ',' + TO2);
assertEquals(expected, result);
result = Address.parseAndPack(mCcView.getText().toString());
expected = Address.parseAndPack(CC1 + ',' + CC3);
assertEquals(expected, result);
}
});
}
/**
* Check that we create the proper to and cc addressees in reply and reply-all, making sure
* to reject duplicate addressees AND the email address of the sending account
*
* In this case, we're doing a "reply all"
* The user is CC2 (a "cc" recipient)
* The to should be: FROM, TO1, TO2, and TO3
* The cc should be: CC3 (CC1/CC4 are duplicates; CC2 is the our account's email address)
*/
public void testReplyAllAddresses3() throws Throwable {
final MessageCompose a = getActivity();
// Doesn't matter what Intent we use here
final Intent intent = new Intent(Intent.ACTION_VIEW);
Message msg = new Message();
final Account account = new Account();
msg.mFrom = Address.parseAndPack(FROM);
msg.mTo = Address.parseAndPack(TO1 + ',' + TO2 + ',' + TO3);
msg.mCc = Address.parseAndPack(CC1 + ',' + CC2 + ',' + CC3 + ',' + CC4);
final Message message = msg;
account.mEmailAddress = "sEcOnD.cC@gOoGlE.cOm";
runTestOnUiThread(new Runnable() {
public void run() {
a.initFromIntent(intent);
a.setupAddressViews(message, account, mToView, mCcView, true);
String result = Address.parseAndPack(mToView.getText().toString());
String expected = Address.parseAndPack(FROM + ',' + TO1 + ',' + TO2 + ',' + TO3);
assertEquals(expected, result);
result = Address.parseAndPack(mCcView.getText().toString());
expected = Address.parseAndPack(CC3);
assertEquals(expected, result);
}
});
}
/**
* Test for processing of Intent EXTRA_* fields that impact the headers:
* Intent.EXTRA_EMAIL, Intent.EXTRA_CC, Intent.EXTRA_BCC, Intent.EXTRA_SUBJECT