First steps towards using the new EmailContent.Message in MessageCompose.

This commit is contained in:
Mihai Preda 2009-07-09 14:46:03 -07:00
parent 6b158f715b
commit c6893ddf0f
8 changed files with 273 additions and 127 deletions

View File

@ -27,6 +27,7 @@ import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import java.util.HashSet;
@ -49,13 +50,6 @@ public class Controller {
};
private static int MESSAGEID_TO_ACCOUNTID_COLUMN_ACCOUNTID = 1;
private static String[] ACCOUNTID_TO_MAILBOXTYPE_PROJECTION = new String[] {
EmailContent.RECORD_ID,
EmailContent.MailboxColumns.ACCOUNT_KEY,
EmailContent.MailboxColumns.TYPE
};
private static int ACCOUNTID_TO_MAILBOXTYPE_COLUMN_ID = 0;
protected Controller(Context _context) {
mContext = _context;
mProviderContext = _context;
@ -177,6 +171,90 @@ public class Controller {
}.start();
}
/**
* Saves the message to a mailbox of given type.
* @param message the message (must have the mAccountId set).
* @param mailboxType the mailbox type (e.g. Mailbox.TYPE_DRAFTS).
* TODO: UI feedback.
* TODO: use AsyncTask instead of Thread
*/
public void saveToMailbox(final EmailContent.Message message, final int mailboxType) {
new Thread() {
@Override
public void run() {
long accountId = message.mAccountKey;
long mailboxId = findOrCreateMailboxOfType(accountId, mailboxType);
message.mMailboxKey = mailboxId;
message.save(mContext);
}
}.start();
}
/**
* @param accountId the account id
* @param mailboxType the mailbox type (e.g. EmailContent.Mailbox.TYPE_TRASH)
* @return the id of the mailbox. The mailbox is created if not existing.
* Returns Mailbox.NO_MAILBOX if the accountId or mailboxType are negative.
* Does not validate the input in other ways (e.g. does not verify the existence of account).
*/
public long findOrCreateMailboxOfType(long accountId, int mailboxType) {
if (accountId < 0 || mailboxType < 0) {
return Mailbox.NO_MAILBOX;
}
long mailboxId =
Mailbox.findMailboxOfType(mProviderContext, accountId, mailboxType);
return mailboxId == Mailbox.NO_MAILBOX ? createMailbox(accountId, mailboxType) : mailboxId;
}
/**
* @param mailboxType the mailbox type
* @return the resource string corresponding to the mailbox type, empty if not found.
*/
/* package */ String getSpecialMailboxDisplayName(int mailboxType) {
int resId = -1;
switch (mailboxType) {
case Mailbox.TYPE_INBOX:
// TODO: there is no special_mailbox_display_name_inbox; why?
resId = R.string.special_mailbox_name_inbox;
break;
case Mailbox.TYPE_OUTBOX:
resId = R.string.special_mailbox_display_name_outbox;
break;
case Mailbox.TYPE_DRAFTS:
resId = R.string.special_mailbox_display_name_drafts;
break;
case Mailbox.TYPE_TRASH:
resId = R.string.special_mailbox_display_name_trash;
break;
case Mailbox.TYPE_SENT:
resId = R.string.special_mailbox_display_name_sent;
break;
}
return resId != -1 ? mContext.getString(resId) : "";
}
/**
* Create a mailbox given the account and mailboxType.
* TODO: Does this need to be signaled explicitly to the sync engines?
* As this method is only used internally ('private'), it does not
* validate its inputs (accountId and mailboxType).
*/
/* package */ long createMailbox(long accountId, int mailboxType) {
if (accountId < 0 || mailboxType < 0) {
String mes = "Invalid arguments " + accountId + ' ' + mailboxType;
Log.e(Email.LOG_TAG, mes);
throw new RuntimeException(mes);
}
Mailbox box = new Mailbox();
box.mAccountKey = accountId;
box.mType = mailboxType;
box.mSyncFrequency = EmailContent.Account.CHECK_INTERVAL_NEVER;
box.mFlagVisible = true;
box.mDisplayName = getSpecialMailboxDisplayName(mailboxType);
box.saveOrUpdate(mProviderContext);
return box.mId;
}
/**
* Delete a single message by moving it to the trash.
*
@ -211,34 +289,9 @@ public class Controller {
}
// 2. Confirm that there is a trash mailbox available
long trashMailboxId = -1;
c = null;
try {
c = resolver.query(EmailContent.Mailbox.CONTENT_URI,
ACCOUNTID_TO_MAILBOXTYPE_PROJECTION,
EmailContent.MailboxColumns.ACCOUNT_KEY + "=? AND " +
EmailContent.MailboxColumns.TYPE + "=" + EmailContent.Mailbox.TYPE_TRASH,
new String[] { Long.toString(accountId) }, null);
if (c.moveToFirst()) {
trashMailboxId = c.getLong(ACCOUNTID_TO_MAILBOXTYPE_COLUMN_ID);
}
} finally {
if (c != null) c.close();
}
// 3. If there's no trash mailbox, create one
// TODO: Does this need to be signaled explicitly to the sync engines?
if (trashMailboxId == -1) {
Mailbox box = new Mailbox();
box.mDisplayName = mContext.getString(R.string.special_mailbox_name_trash);
box.mAccountKey = accountId;
box.mType = Mailbox.TYPE_TRASH;
box.mSyncFrequency = EmailContent.Account.CHECK_INTERVAL_NEVER;
box.mFlagVisible = true;
box.saveOrUpdate(mProviderContext);
trashMailboxId = box.mId;
}
long trashMailboxId = findOrCreateMailboxOfType(accountId, Mailbox.TYPE_TRASH);
// 4. Change the mailbox key for the message we're "deleting"
ContentValues cv = new ContentValues();

View File

@ -20,7 +20,6 @@ import com.android.email.activity.AccountShortcutPicker;
import com.android.email.activity.MessageCompose;
import com.android.email.mail.internet.BinaryTempFileBody;
import com.android.email.provider.EmailContent;
import com.android.email.provider.EmailContent;
import com.android.email.service.BootReceiver;
import com.android.email.service.MailService;

View File

@ -23,6 +23,7 @@ import com.android.email.MessagingController;
import com.android.email.MessagingListener;
import com.android.email.R;
import com.android.email.Utility;
import com.android.email.Controller;
import com.android.email.mail.Address;
import com.android.email.mail.Body;
import com.android.email.mail.Message;
@ -140,6 +141,8 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
private ImageButton mQuotedTextDelete;
private WebView mQuotedText;
private Controller mController;
private boolean mDraftNeedsSaving = false;
/**
@ -292,6 +295,8 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
setContentView(R.layout.message_compose);
mController = Controller.getInstance(getApplication());
mAddressAdapter = new EmailAddressAdapter(this);
mAddressValidator = new EmailAddressValidator();
@ -445,6 +450,7 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
mAccountId = intent.getLongExtra(EXTRA_ACCOUNT_ID, -1);
mAccount = EmailContent.Account.restoreAccountWithId(this, mAccountId);
mFolder = intent.getStringExtra(EXTRA_FOLDER);
// TODO: change sourceMessageUid to be long _id instead of String
mSourceMessageUid = intent.getStringExtra(EXTRA_MESSAGE);
}
@ -577,27 +583,31 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
view.append(address + ", ");
}
private String getPackedAddresses(TextView view) {
Address[] addresses = Address.parse(view.getText().toString().trim());
return Address.pack(addresses);
}
private Address[] getAddresses(TextView view) {
Address[] addresses = Address.parse(view.getText().toString().trim());
return addresses;
}
private MimeMessage createMessage() throws MessagingException {
MimeMessage message = new MimeMessage();
message.setSentDate(new Date());
Address from = new Address(mAccount.getEmail(), mAccount.getName());
message.setFrom(from);
message.setRecipients(RecipientType.TO, getAddresses(mToView));
message.setRecipients(RecipientType.CC, getAddresses(mCcView));
message.setRecipients(RecipientType.BCC, getAddresses(mBccView));
message.setSubject(mSubjectView.getText().toString());
private EmailContent.Message createMessage() throws MessagingException {
EmailContent.Message message = new EmailContent.Message();
message.mTimeStamp = System.currentTimeMillis();
message.mFrom = new Address(mAccount.getEmail(), mAccount.getName()).pack();
message.mTo = getPackedAddresses(mToView);
message.mCc = getPackedAddresses(mCcView);
message.mBcc = getPackedAddresses(mBccView);
message.mSubject = mSubjectView.getText().toString();
// Preserve Message-ID header if found
// This makes sure that multiply-saved drafts are identified as the same message
if (mSourceMessage != null && mSourceMessage instanceof MimeMessage) {
String messageIdHeader = ((MimeMessage)mSourceMessage).getMessageId();
if (messageIdHeader != null) {
message.setMessageId(messageIdHeader);
message.mMessageId = messageIdHeader;
}
}
@ -639,51 +649,53 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
}
}
TextBody body = new TextBody(text);
if (mAttachments.getChildCount() > 0) {
/*
* The message has attachments that need to be included. First we add the part
* containing the text that will be sent and then we include each attachment.
*/
MimeMultipart mp;
mp = new MimeMultipart();
mp.addBodyPart(new MimeBodyPart(body, "text/plain"));
for (int i = 0, count = mAttachments.getChildCount(); i < count; i++) {
Attachment attachment = (Attachment) mAttachments.getChildAt(i).getTag();
MimeBodyPart bp = new MimeBodyPart(
new LocalStore.LocalAttachmentBody(attachment.uri, getApplication()));
bp.setHeader(MimeHeader.HEADER_CONTENT_TYPE, String.format("%s;\n name=\"%s\"",
attachment.contentType,
attachment.name));
bp.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64");
bp.setHeader(MimeHeader.HEADER_CONTENT_DISPOSITION,
String.format("attachment;\n filename=\"%s\"",
attachment.name));
mp.addBodyPart(bp);
}
message.setBody(mp);
}
else {
/*
* No attachments to include, just stick the text body in the message and call
* it good.
*/
message.setBody(body);
}
message.mText = text;
message.mAccountKey = mAccountId;
// TODO: add attachments (as below)
return message;
}
// TextBody body = new TextBody(text);
// if (mAttachments.getChildCount() > 0) {
// /*
// * The message has attachments that need to be included. First we add the part
// * containing the text that will be sent and then we include each attachment.
// */
// MimeMultipart mp;
// mp = new MimeMultipart();
// mp.addBodyPart(new MimeBodyPart(body, "text/plain"));
// for (int i = 0, count = mAttachments.getChildCount(); i < count; i++) {
// Attachment attachment = (Attachment) mAttachments.getChildAt(i).getTag();
// MimeBodyPart bp = new MimeBodyPart(
// new LocalStore.LocalAttachmentBody(attachment.uri, getApplication()));
// bp.setHeader(MimeHeader.HEADER_CONTENT_TYPE, String.format("%s;\n name=\"%s\"",
// attachment.contentType,
// attachment.name));
// bp.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64");
// bp.setHeader(MimeHeader.HEADER_CONTENT_DISPOSITION,
// String.format("attachment;\n filename=\"%s\"",
// attachment.name));
// mp.addBodyPart(bp);
// }
// message.setBody(mp);
// }
// else {
// /*
// * No attachments to include, just stick the text body in the message and call
// * it good.
// */
// message.setBody(body);
// }
private void sendOrSaveMessage(boolean save) {
/*
* Create the message from all the data the user has entered.
*/
MimeMessage message;
EmailContent.Message message;
try {
message = createMessage();
}
@ -696,44 +708,46 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
/*
* Save a draft
*/
if (mDraftUid != null) {
message.setUid(mDraftUid);
}
else if (ACTION_EDIT_DRAFT.equals(getIntent().getAction())) {
/*
* We're saving a previously saved draft, so update the new message's uid
* to the old message's uid.
*/
message.setUid(mSourceMessageUid);
}
MessagingController.getInstance(getApplication()).saveDraft(mAccount, message);
mDraftUid = message.getUid();
mController.saveToMailbox(message, EmailContent.Mailbox.TYPE_DRAFTS);
// if (mDraftUid != null) {
// message.setUid(mDraftUid);
// }
// else if (ACTION_EDIT_DRAFT.equals(getIntent().getAction())) {
// /*
// * We're saving a previously saved draft, so update the new message's uid
// * to the old message's uid.
// */
// message.setUid(mSourceMessageUid);
// }
// MessagingController.getInstance(getApplication()).saveDraft(mAccount, message);
// mDraftUid = message.getUid();
// Don't display the toast if the user is just changing the orientation
if ((getChangingConfigurations() & ActivityInfo.CONFIG_ORIENTATION) == 0) {
mHandler.sendEmptyMessage(MSG_SAVED_DRAFT);
}
}
else {
/*
* Send the message
* If the source message is in other folder than draft, it should not be deleted while
* sending message.
*/
if (ACTION_EDIT_DRAFT.equals(getIntent().getAction())
&& mSourceMessageUid != null
&& mFolder.equals(mAccount.getDraftsFolderName(this))) {
/*
* We're sending a previously saved draft, so delete the old draft first.
*/
MessagingController.getInstance(getApplication()).deleteMessage(
mAccount,
mFolder,
mSourceMessage,
null);
}
MessagingController.getInstance(getApplication()).sendMessage(mAccount, message, null);
}
// else {
// /*
// * Send the message
// * If the source message is in other folder than draft, it should not be deleted while
// * sending message.
// */
// if (ACTION_EDIT_DRAFT.equals(getIntent().getAction())
// && mSourceMessageUid != null
// && mFolder.equals(mAccount.getDraftsFolderName(this))) {
// /*
// * We're sending a previously saved draft, so delete the old draft first.
// */
// MessagingController.getInstance(getApplication()).deleteMessage(
// mAccount,
// mFolder,
// mSourceMessage,
// null);
// }
// MessagingController.getInstance(getApplication()).sendMessage(mAccount, message, null);
// }
}
private void saveIfNeeded() {

View File

@ -366,6 +366,9 @@ public class Address {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < nAddr; i++) {
if (i != 0) {
sb.append(LIST_DELIMITER_EMAIL);
}
final Address address = addresses[i];
sb.append(address.getAddress());
final String displayName = address.getPersonal();
@ -373,13 +376,23 @@ public class Address {
sb.append(LIST_DELIMITER_PERSONAL);
sb.append(displayName);
}
if (i < nAddr - 1) {
sb.append(LIST_DELIMITER_EMAIL);
}
}
return sb.toString();
}
/**
* Produces the same result as pack(array), but only packs one (this) address.
*/
public String pack() {
final String address = getAddress();
final String personal = getPersonal();
if (personal == null) {
return address;
} else {
return address + LIST_DELIMITER_PERSONAL + personal;
}
}
/**
* Legacy unpack() used for reading the old data (migration),
* as found in LocalStore (Donut; db version up to 24).

View File

@ -337,7 +337,7 @@ public abstract class EmailContent {
// Foreign key to a referenced Message (e.g. for a reply/forward)
public static final String REFERENCE_KEY = "referenceKey";
// Address lists, of the form <address> [, <address> ...]
// Address lists, packed with Address.pack()
public static final String SENDER_LIST = "senderList";
public static final String FROM_LIST = "fromList";
public static final String TO_LIST = "toList";
@ -487,9 +487,6 @@ public abstract class EmailContent {
public static final int LOADED = 1;
public static final int PARTIALLY_LOADED = 2;
/**
* no public constructor since this is a utility class
*/
public Message() {
mBaseUri = CONTENT_URI;
}
@ -1629,6 +1626,7 @@ public abstract class EmailContent {
MailboxColumns.SYNC_FREQUENCY, MailboxColumns.SYNC_TIME,MailboxColumns.UNREAD_COUNT,
MailboxColumns.FLAG_VISIBLE, MailboxColumns.FLAGS, MailboxColumns.VISIBLE_LIMIT
};
public static final long NO_MAILBOX = -1;
private static final String WHERE_TYPE_AND_ACCOUNT_KEY =
MailboxColumns.TYPE + "=? and " + MailboxColumns.ACCOUNT_KEY + "=?";
@ -1636,14 +1634,10 @@ public abstract class EmailContent {
private static final int ID_PROJECTION_ID = 0;
private static final String[] ID_PROJECTION = new String[] { ID };
/**
* no public constructor since this is a utility class
*/
public Mailbox() {
mBaseUri = CONTENT_URI;
}
// Types of mailboxes. The list is ordered to match a typical UI presentation, e.g.
// placing the inbox at the top.
// The "main" mailbox for the account, almost always referred to as "Inbox"
@ -1746,7 +1740,7 @@ public abstract class EmailContent {
* @return the id of the mailbox, or -1 if not found
*/
public static long findMailboxOfType(Context context, long accountId, int type) {
long mailboxId = -1;
long mailboxId = NO_MAILBOX;
String[] bindArguments = new String[] {Long.toString(type), Long.toString(accountId)};
Cursor c = context.getContentResolver().query(Mailbox.CONTENT_URI,
ID_PROJECTION, WHERE_TYPE_AND_ACCOUNT_KEY, bindArguments, null);

View File

@ -57,7 +57,8 @@ public class EmailProvider extends ContentProvider {
// In these early versions, updating the database version will cause all tables to be deleted
// Obviously, we'll handle upgrades differently once things are a bit stable
public static final int DATABASE_VERSION = 14;
// version 15: changed Address.pack() format.
public static final int DATABASE_VERSION = 15;
public static final int BODY_DATABASE_VERSION = 1;
public static final String EMAIL_AUTHORITY = "com.android.email.provider";

View File

@ -69,6 +69,68 @@ public class ControllerProviderOpsTests extends ProviderTestCase2<EmailProvider>
}
}
public void testGetSpecialMailboxName() {
Controller ct = new TestController(mProviderContext, mContext);
assertEquals("Outbox", ct.getSpecialMailboxDisplayName(Mailbox.TYPE_OUTBOX));
assertEquals("", ct.getSpecialMailboxDisplayName(-1));
}
/**
* Test of Controller.createMailbox().
* Sunny day test only - creates a mailbox that does not exist.
* Does not test duplication, bad accountID, or any other bad input.
*/
public void testCreateMailbox() {
Account account = ProviderTestUtils.setupAccount("mailboxid", true, mProviderContext);
long accountId = account.mId;
long oldBoxId = Mailbox.findMailboxOfType(mProviderContext, accountId, Mailbox.TYPE_DRAFTS);
assertEquals(Mailbox.NO_MAILBOX, oldBoxId);
Controller ct = new TestController(mProviderContext, mContext);
ct.createMailbox(accountId, Mailbox.TYPE_DRAFTS);
long boxId = Mailbox.findMailboxOfType(mProviderContext, accountId, Mailbox.TYPE_DRAFTS);
// check that the drafts mailbox exists
assertTrue("mailbox exists", boxId != Mailbox.NO_MAILBOX);
}
/**
* Test of Controller.findOrCreateMailboxOfType().
* Checks:
* - finds correctly the ID of existing mailbox
* - creates non-existing mailbox
* - creates only once a new mailbox
* - when accountId or mailboxType are -1, returns NO_MAILBOX
*/
public void testFindOrCreateMailboxOfType() {
Account account = ProviderTestUtils.setupAccount("mailboxid", true, mProviderContext);
long accountId = account.mId;
Mailbox box = ProviderTestUtils.setupMailbox("box", accountId, false, mProviderContext);
final int boxType = Mailbox.TYPE_TRASH;
box.mType = boxType;
box.save(mProviderContext);
long boxId = box.mId;
Controller ct = new TestController(mProviderContext, mContext);
long testBoxId = ct.findOrCreateMailboxOfType(accountId, boxType);
// check it found the right mailbox id
assertEquals(boxId, testBoxId);
long boxId2 = ct.findOrCreateMailboxOfType(accountId, Mailbox.TYPE_DRAFTS);
assertTrue("mailbox created", boxId2 != Mailbox.NO_MAILBOX);
assertTrue("with different id", testBoxId != boxId2);
// check it doesn't create twice when existing
long boxId3 = ct.findOrCreateMailboxOfType(accountId, Mailbox.TYPE_DRAFTS);
assertEquals("don't create if exists", boxId3, boxId2);
// check invalid aruments
assertEquals(Mailbox.NO_MAILBOX, ct.findOrCreateMailboxOfType(-1, Mailbox.TYPE_DRAFTS));
assertEquals(Mailbox.NO_MAILBOX, ct.findOrCreateMailboxOfType(accountId, -1));
}
/**
* Test the "delete message" function. Sunny day:
* - message/mailbox/account all exist

View File

@ -560,6 +560,16 @@ public class AddressUnitTests extends AndroidTestCase {
}
}
public void testSinglePack() {
Address[] addrArray = new Address[1];
for (Address address : new Address[]{PACK_ADDR_1, PACK_ADDR_2, PACK_ADDR_3}) {
String packed1 = address.pack();
addrArray[0] = address;
String packed2 = Address.pack(addrArray);
assertEquals(packed1, packed2);
}
}
public void testIsValidAddress() {
String notValid[] = {"", "foo", "john@", "x@y", "x@y.", "foo.com"};
String valid[] = {"x@y.z", "john@gmail.com", "a@b.c.d"};