Enable message upload
* Create logic to detect upsyncable messages in Sent * Note: Drafts is now local only for IMAP - no sync, either way * Rewrite MessageController.processPendingAppend for Provider world * Write provider message -> legacy message converter * Fixed bug in IMAP APPEND (it was not picking the right UID for the uploaded message.) * Better handling of server internaldate * Add constants for new X-Android-Body-Quoted-Part header * Add EmailContent routines to get each of the 5 parts of the body * Remove "Load more" from unsynced message lists * Add toString to MimeHeader for debug support Bug # 2097471 TODO (next CL): Upload attachments records too Change-Id: I209182f5adc6b6696919f559e3cbbdd58b3eed3a
This commit is contained in:
parent
1086473056
commit
c41c47fa07
@ -21,8 +21,13 @@ import com.android.email.mail.Flag;
|
||||
import com.android.email.mail.Message;
|
||||
import com.android.email.mail.MessagingException;
|
||||
import com.android.email.mail.Part;
|
||||
import com.android.email.mail.Message.RecipientType;
|
||||
import com.android.email.mail.internet.MimeBodyPart;
|
||||
import com.android.email.mail.internet.MimeHeader;
|
||||
import com.android.email.mail.internet.MimeMessage;
|
||||
import com.android.email.mail.internet.MimeMultipart;
|
||||
import com.android.email.mail.internet.MimeUtility;
|
||||
import com.android.email.mail.internet.TextBody;
|
||||
import com.android.email.provider.AttachmentProvider;
|
||||
import com.android.email.provider.EmailContent;
|
||||
import com.android.email.provider.EmailContent.Attachment;
|
||||
@ -34,6 +39,7 @@ import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
@ -44,9 +50,16 @@ import java.util.Date;
|
||||
|
||||
public class LegacyConversions {
|
||||
|
||||
/**
|
||||
* Values for HEADER_ANDROID_BODY_QUOTED_PART to tag body parts
|
||||
*/
|
||||
/* package */ static final String BODY_QUOTED_PART_REPLY = "quoted-reply";
|
||||
/* package */ static final String BODY_QUOTED_PART_FORWARD = "quoted-forward";
|
||||
/* package */ static final String BODY_QUOTED_PART_INTRO = "quoted-intro";
|
||||
|
||||
/**
|
||||
* Copy field-by-field from a "store" message to a "provider" message
|
||||
* @param message The message we've just downloaded
|
||||
* @param message The message we've just downloaded (must be a MimeMessage)
|
||||
* @param localMessage The message we'd like to write into the DB
|
||||
* @result true if dirty (changes were made)
|
||||
*/
|
||||
@ -91,7 +104,7 @@ public class LegacyConversions {
|
||||
localMessage.mServerTimeStamp = internalDate.getTime();
|
||||
}
|
||||
// public String mClientId;
|
||||
// public String mMessageId;
|
||||
localMessage.mMessageId = ((MimeMessage)message).getMessageId();
|
||||
|
||||
// public long mBodyKey;
|
||||
localMessage.mMailboxKey = mailboxId;
|
||||
@ -126,33 +139,90 @@ public class LegacyConversions {
|
||||
|
||||
body.mMessageKey = localMessage.mId;
|
||||
|
||||
StringBuffer sbHtml = new StringBuffer();
|
||||
StringBuffer sbText = new StringBuffer();
|
||||
StringBuffer sbHtml = null;
|
||||
StringBuffer sbText = null;
|
||||
StringBuffer sbHtmlReply = null;
|
||||
StringBuffer sbTextReply = null;
|
||||
StringBuffer sbIntroText = null;
|
||||
|
||||
for (Part viewable : viewables) {
|
||||
String text = MimeUtility.getTextFromPart(viewable);
|
||||
if ("text/html".equalsIgnoreCase(viewable.getMimeType())) {
|
||||
if (sbHtml.length() > 0) {
|
||||
sbHtml.append('\n');
|
||||
String[] replyTags = viewable.getHeader(MimeHeader.HEADER_ANDROID_BODY_QUOTED_PART);
|
||||
String replyTag = null;
|
||||
if (replyTags != null && replyTags.length > 0) {
|
||||
replyTag = replyTags[0];
|
||||
}
|
||||
// Deploy text as marked by the various tags
|
||||
boolean isHtml = "text/html".equalsIgnoreCase(viewable.getMimeType());
|
||||
|
||||
if (replyTag != null) {
|
||||
boolean isQuotedReply = BODY_QUOTED_PART_REPLY.equalsIgnoreCase(replyTag);
|
||||
boolean isQuotedForward = BODY_QUOTED_PART_FORWARD.equalsIgnoreCase(replyTag);
|
||||
boolean isQuotedIntro = BODY_QUOTED_PART_INTRO.equalsIgnoreCase(replyTag);
|
||||
|
||||
if (isQuotedReply || isQuotedForward) {
|
||||
if (isHtml) {
|
||||
sbHtmlReply = appendTextPart(sbHtmlReply, text);
|
||||
} else {
|
||||
sbTextReply = appendTextPart(sbTextReply, text);
|
||||
}
|
||||
// Set message flags as well
|
||||
localMessage.mFlags &= ~EmailContent.Message.FLAG_TYPE_MASK;
|
||||
localMessage.mFlags |= isQuotedReply
|
||||
? EmailContent.Message.FLAG_TYPE_REPLY
|
||||
: EmailContent.Message.FLAG_TYPE_FORWARD;
|
||||
continue;
|
||||
}
|
||||
sbHtml.append(text);
|
||||
if (isQuotedIntro) {
|
||||
sbIntroText = appendTextPart(sbIntroText, text);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Most of the time, just process regular body parts
|
||||
if (isHtml) {
|
||||
sbHtml = appendTextPart(sbHtml, text);
|
||||
} else {
|
||||
if (sbText.length() > 0) {
|
||||
sbText.append('\n');
|
||||
}
|
||||
sbText.append(text);
|
||||
sbText = appendTextPart(sbText, text);
|
||||
}
|
||||
}
|
||||
|
||||
// write the combined data to the body part
|
||||
if (sbText.length() != 0) {
|
||||
if (sbText != null && sbText.length() != 0) {
|
||||
body.mTextContent = sbText.toString();
|
||||
}
|
||||
if (sbHtml.length() != 0) {
|
||||
if (sbHtml != null && sbHtml.length() != 0) {
|
||||
body.mHtmlContent = sbHtml.toString();
|
||||
}
|
||||
if (sbHtmlReply != null && sbHtmlReply.length() != 0) {
|
||||
body.mHtmlReply = sbHtmlReply.toString();
|
||||
}
|
||||
if (sbTextReply != null && sbTextReply.length() != 0) {
|
||||
body.mTextReply = sbTextReply.toString();
|
||||
}
|
||||
if (sbIntroText != null && sbIntroText.length() != 0) {
|
||||
body.mIntroText = sbIntroText.toString();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to append text to a StringBuffer, creating it if necessary.
|
||||
* Optimization: The majority of the time we are *not* appending - we should have a path
|
||||
* that deals with single strings.
|
||||
*/
|
||||
private static StringBuffer appendTextPart(StringBuffer sb, String newText) {
|
||||
if (sb == null) {
|
||||
sb = new StringBuffer(newText);
|
||||
} else {
|
||||
if (sb.length() > 0) {
|
||||
sb.append('\n');
|
||||
}
|
||||
sb.append(newText);
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy attachments from MimeMessage to provider Message.
|
||||
*
|
||||
@ -269,4 +339,116 @@ public class LegacyConversions {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a complete Provider message into a legacy message (for IMAP upload). This
|
||||
* is basically the equivalent of LocalFolder.getMessages() + LocalFolder.fetch().
|
||||
*/
|
||||
public static Message makeMessage(Context context, EmailContent.Message localMessage)
|
||||
throws MessagingException {
|
||||
MimeMessage message = new MimeMessage();
|
||||
|
||||
// LocalFolder.getMessages() equivalent: Copy message fields
|
||||
message.setSubject(localMessage.mSubject == null ? "" : localMessage.mSubject);
|
||||
Address[] from = Address.unpack(localMessage.mFrom);
|
||||
if (from.length > 0) {
|
||||
message.setFrom(from[0]);
|
||||
}
|
||||
message.setSentDate(new Date(localMessage.mTimeStamp));
|
||||
message.setUid(localMessage.mServerId);
|
||||
message.setFlag(Flag.DELETED,
|
||||
localMessage.mFlagLoaded == EmailContent.Message.FLAG_LOADED_DELETED);
|
||||
message.setFlag(Flag.SEEN, localMessage.mFlagRead);
|
||||
message.setFlag(Flag.FLAGGED, localMessage.mFlagFavorite);
|
||||
// message.setFlag(Flag.DRAFT, localMessage.mMailboxKey == draftMailboxKey);
|
||||
message.setRecipients(RecipientType.TO, Address.unpack(localMessage.mTo));
|
||||
message.setRecipients(RecipientType.CC, Address.unpack(localMessage.mCc));
|
||||
message.setRecipients(RecipientType.BCC, Address.unpack(localMessage.mBcc));
|
||||
message.setReplyTo(Address.unpack(localMessage.mReplyTo));
|
||||
message.setInternalDate(new Date(localMessage.mServerTimeStamp));
|
||||
message.setMessageId(localMessage.mMessageId);
|
||||
|
||||
// LocalFolder.fetch() equivalent: build body parts
|
||||
message.setHeader(MimeHeader.HEADER_CONTENT_TYPE, "multipart/mixed");
|
||||
MimeMultipart mp = new MimeMultipart();
|
||||
mp.setSubType("mixed");
|
||||
message.setBody(mp);
|
||||
|
||||
try {
|
||||
addTextBodyPart(mp, "text/html", null,
|
||||
EmailContent.Body.restoreBodyHtmlWithMessageId(context, localMessage.mId));
|
||||
} catch (RuntimeException rte) {
|
||||
Log.d(Email.LOG_TAG, "Exception while reading html body " + rte.toString());
|
||||
}
|
||||
|
||||
try {
|
||||
addTextBodyPart(mp, "text/plain", null,
|
||||
EmailContent.Body.restoreBodyTextWithMessageId(context, localMessage.mId));
|
||||
} catch (RuntimeException rte) {
|
||||
Log.d(Email.LOG_TAG, "Exception while reading text body " + rte.toString());
|
||||
}
|
||||
|
||||
boolean isReply = (localMessage.mFlags & EmailContent.Message.FLAG_TYPE_REPLY) != 0;
|
||||
boolean isForward = (localMessage.mFlags & EmailContent.Message.FLAG_TYPE_FORWARD) != 0;
|
||||
|
||||
// If there is a quoted part (forwarding or reply), add the intro first, and then the
|
||||
// rest of it. If it is opened in some other viewer, it will (hopefully) be displayed in
|
||||
// the same order as we've just set up the blocks: composed text, intro, replied text
|
||||
if (isReply || isForward) {
|
||||
try {
|
||||
addTextBodyPart(mp, "text/plain", BODY_QUOTED_PART_INTRO,
|
||||
EmailContent.Body.restoreIntroTextWithMessageId(context, localMessage.mId));
|
||||
} catch (RuntimeException rte) {
|
||||
Log.d(Email.LOG_TAG, "Exception while reading text reply " + rte.toString());
|
||||
}
|
||||
|
||||
String replyTag = isReply ? BODY_QUOTED_PART_REPLY : BODY_QUOTED_PART_FORWARD;
|
||||
try {
|
||||
addTextBodyPart(mp, "text/html", replyTag,
|
||||
EmailContent.Body.restoreReplyHtmlWithMessageId(context, localMessage.mId));
|
||||
} catch (RuntimeException rte) {
|
||||
Log.d(Email.LOG_TAG, "Exception while reading html reply " + rte.toString());
|
||||
}
|
||||
|
||||
try {
|
||||
addTextBodyPart(mp, "text/plain", replyTag,
|
||||
EmailContent.Body.restoreReplyTextWithMessageId(context, localMessage.mId));
|
||||
} catch (RuntimeException rte) {
|
||||
Log.d(Email.LOG_TAG, "Exception while reading text reply " + rte.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// Attachments
|
||||
// TODO: Make sure we deal with these as structures and don't accidentally upload files
|
||||
// Uri uri = ContentUris.withAppendedId(Attachment.MESSAGE_ID_URI, localMessage.mId);
|
||||
// Cursor attachments = context.getContentResolver().query(uri, Attachment.CONTENT_PROJECTION,
|
||||
// null, null, null);
|
||||
// try {
|
||||
//
|
||||
// } finally {
|
||||
// attachments.close();
|
||||
// }
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to add a body part for a given type of text, if found
|
||||
*
|
||||
* @param mp The text body part will be added to this multipart
|
||||
* @param contentType The content-type of the text being added
|
||||
* @param quotedPartTag If non-null, HEADER_ANDROID_BODY_QUOTED_PART will be set to this value
|
||||
* @param partText The text to add. If null, nothing happens
|
||||
*/
|
||||
private static void addTextBodyPart(MimeMultipart mp, String contentType, String quotedPartTag,
|
||||
String partText) throws MessagingException {
|
||||
if (partText == null) {
|
||||
return;
|
||||
}
|
||||
TextBody body = new TextBody(partText);
|
||||
MimeBodyPart bp = new MimeBodyPart(body, contentType);
|
||||
if (quotedPartTag != null) {
|
||||
bp.addHeader(MimeHeader.HEADER_ANDROID_BODY_QUOTED_PART, quotedPartTag);
|
||||
}
|
||||
mp.addBodyPart(bp);
|
||||
}
|
||||
}
|
||||
|
@ -33,10 +33,6 @@ import com.android.email.mail.internet.MimeBodyPart;
|
||||
import com.android.email.mail.internet.MimeHeader;
|
||||
import com.android.email.mail.internet.MimeMultipart;
|
||||
import com.android.email.mail.internet.MimeUtility;
|
||||
import com.android.email.mail.store.LocalStore;
|
||||
import com.android.email.mail.store.LocalStore.LocalFolder;
|
||||
import com.android.email.mail.store.LocalStore.LocalMessage;
|
||||
import com.android.email.mail.store.LocalStore.PendingCommand;
|
||||
import com.android.email.provider.AttachmentProvider;
|
||||
import com.android.email.provider.EmailContent;
|
||||
import com.android.email.provider.EmailContent.Attachment;
|
||||
@ -77,6 +73,7 @@ import java.util.concurrent.LinkedBlockingQueue;
|
||||
* removed from the queue once the activity is no longer active.
|
||||
*/
|
||||
public class MessagingController implements Runnable {
|
||||
|
||||
/**
|
||||
* The maximum message size that we'll consider to be "small". A small message is downloaded
|
||||
* in full immediately instead of in pieces. Anything over this size will be downloaded in
|
||||
@ -100,6 +97,11 @@ public class MessagingController implements Runnable {
|
||||
private static Flag[] FLAG_LIST_SEEN = new Flag[] { Flag.SEEN };
|
||||
private static Flag[] FLAG_LIST_FLAGGED = new Flag[] { Flag.FLAGGED };
|
||||
|
||||
/**
|
||||
* We write this into the serverId field of messages that will never be upsynced.
|
||||
*/
|
||||
private static final String LOCAL_SERVERID_PREFIX = "Local-";
|
||||
|
||||
/**
|
||||
* Projections & CVs used by pruneCachedAttachments
|
||||
*/
|
||||
@ -415,10 +417,7 @@ public class MessagingController implements Runnable {
|
||||
StoreSynchronizer.SyncResults results;
|
||||
|
||||
// Select generic sync or store-specific sync
|
||||
final LocalStore localStore =
|
||||
(LocalStore) Store.getInstance(account.getLocalStoreUri(mContext), mContext, null);
|
||||
Store remoteStore = Store.getInstance(account.getStoreUri(mContext), mContext,
|
||||
localStore.getPersistentCallbacks());
|
||||
Store remoteStore = Store.getInstance(account.getStoreUri(mContext), mContext, null);
|
||||
StoreSynchronizer customSync = remoteStore.getMessageSynchronizer();
|
||||
if (customSync == null) {
|
||||
results = synchronizeMailboxGeneric(account, folder);
|
||||
@ -497,6 +496,12 @@ public class MessagingController implements Runnable {
|
||||
Log.d(Email.LOG_TAG, "*** synchronizeMailboxGeneric ***");
|
||||
ContentResolver resolver = mContext.getContentResolver();
|
||||
|
||||
// 0. We do not ever sync DRAFTS or OUTBOX (down or up)
|
||||
if (folder.mType == Mailbox.TYPE_DRAFTS || folder.mType == Mailbox.TYPE_OUTBOX) {
|
||||
int totalMessages = EmailContent.count(mContext, folder.getUri(), null, null);
|
||||
return new StoreSynchronizer.SyncResults(totalMessages, 0);
|
||||
}
|
||||
|
||||
// 1. Get the message list from the local store and create an index of the uids
|
||||
|
||||
Cursor localUidCursor = null;
|
||||
@ -1069,10 +1074,10 @@ public class MessagingController implements Runnable {
|
||||
* Handles:
|
||||
* Read/Unread
|
||||
* Flagged
|
||||
* Append (upload)
|
||||
* Move To Trash
|
||||
* Empty trash
|
||||
* TODO:
|
||||
* Append
|
||||
* Move
|
||||
*
|
||||
* @param account the account to scan for pending actions
|
||||
@ -1086,6 +1091,9 @@ public class MessagingController implements Runnable {
|
||||
// Handle deletes first, it's always better to get rid of things first
|
||||
processPendingDeletesSynchronous(account, resolver, accountIdArgs);
|
||||
|
||||
// Handle uploads (currently, only to sent messages)
|
||||
processPendingUploadsSynchronous(account, resolver, accountIdArgs);
|
||||
|
||||
// Now handle updates / upsyncs
|
||||
processPendingUpdatesSynchronous(account, resolver, accountIdArgs);
|
||||
}
|
||||
@ -1154,6 +1162,109 @@ public class MessagingController implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan for messages that are in Sent, and are in need of upload,
|
||||
* and send them to the server. "In need of upload" is defined as:
|
||||
* serverId == null (no UID has been assigned)
|
||||
* or
|
||||
* message is in the updated list
|
||||
*
|
||||
* Note we also look for messages that are moving from drafts->outbox->sent. They never
|
||||
* go through "drafts" or "outbox" on the server, so we hang onto these until they can be
|
||||
* uploaded directly to the Sent folder.
|
||||
*
|
||||
* @param account
|
||||
* @param resolver
|
||||
* @param accountIdArgs
|
||||
*/
|
||||
private void processPendingUploadsSynchronous(EmailContent.Account account,
|
||||
ContentResolver resolver, String[] accountIdArgs) throws MessagingException {
|
||||
// Find the Sent folder (since that's all we're uploading for now
|
||||
Cursor mailboxes = resolver.query(Mailbox.CONTENT_URI, Mailbox.ID_PROJECTION,
|
||||
MailboxColumns.ACCOUNT_KEY + "=?"
|
||||
+ " and " + MailboxColumns.TYPE + "=" + Mailbox.TYPE_SENT,
|
||||
accountIdArgs, null);
|
||||
long lastMessageId = -1;
|
||||
try {
|
||||
// Defer setting up the store until we know we need to access it
|
||||
Store remoteStore = null;
|
||||
while (mailboxes.moveToNext()) {
|
||||
long mailboxId = mailboxes.getLong(Mailbox.ID_PROJECTION_COLUMN);
|
||||
String[] mailboxKeyArgs = new String[] { Long.toString(mailboxId) };
|
||||
// Demand load mailbox
|
||||
Mailbox mailbox = null;
|
||||
|
||||
// First handle the "new" messages (serverId == null)
|
||||
Cursor upsyncs1 = resolver.query(EmailContent.Message.CONTENT_URI,
|
||||
EmailContent.Message.ID_PROJECTION,
|
||||
EmailContent.Message.MAILBOX_KEY + "=?"
|
||||
+ " and (" + EmailContent.Message.SERVER_ID + " is null"
|
||||
+ " or " + EmailContent.Message.SERVER_ID + "=''" + ")",
|
||||
mailboxKeyArgs,
|
||||
null);
|
||||
try {
|
||||
while (upsyncs1.moveToNext()) {
|
||||
// Load the remote store if it will be needed
|
||||
if (remoteStore == null) {
|
||||
remoteStore =
|
||||
Store.getInstance(account.getStoreUri(mContext), mContext, null);
|
||||
}
|
||||
// Load the mailbox if it will be needed
|
||||
if (mailbox == null) {
|
||||
mailbox = Mailbox.restoreMailboxWithId(mContext, mailboxId);
|
||||
}
|
||||
// upsync the message
|
||||
long id = upsyncs1.getLong(EmailContent.Message.ID_PROJECTION_COLUMN);
|
||||
lastMessageId = id;
|
||||
processUploadMessage(resolver, remoteStore, account, mailbox, id);
|
||||
}
|
||||
} finally {
|
||||
if (upsyncs1 != null) {
|
||||
upsyncs1.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Next, handle any updates (e.g. edited in place, although this shouldn't happen)
|
||||
Cursor upsyncs2 = resolver.query(EmailContent.Message.UPDATED_CONTENT_URI,
|
||||
EmailContent.Message.ID_PROJECTION,
|
||||
EmailContent.MessageColumns.MAILBOX_KEY + "=?", mailboxKeyArgs,
|
||||
null);
|
||||
try {
|
||||
while (upsyncs2.moveToNext()) {
|
||||
// Load the remote store if it will be needed
|
||||
if (remoteStore == null) {
|
||||
remoteStore =
|
||||
Store.getInstance(account.getStoreUri(mContext), mContext, null);
|
||||
}
|
||||
// Load the mailbox if it will be needed
|
||||
if (mailbox == null) {
|
||||
mailbox = Mailbox.restoreMailboxWithId(mContext, mailboxId);
|
||||
}
|
||||
// upsync the message
|
||||
long id = upsyncs2.getLong(EmailContent.Message.ID_PROJECTION_COLUMN);
|
||||
lastMessageId = id;
|
||||
processUploadMessage(resolver, remoteStore, account, mailbox, id);
|
||||
}
|
||||
} finally {
|
||||
if (upsyncs2 != null) {
|
||||
upsyncs2.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (MessagingException me) {
|
||||
// Presumably an error here is an account connection failure, so there is
|
||||
// no point in continuing through the rest of the pending updates.
|
||||
if (Email.DEBUG) {
|
||||
Log.d(Email.LOG_TAG, "Unable to process pending upsync for id="
|
||||
+ lastMessageId + ": " + me);
|
||||
}
|
||||
} finally {
|
||||
if (mailboxes != null) {
|
||||
mailboxes.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan for messages that are in the Message_Updates table, look for differences that
|
||||
* we can deal with, and do the work.
|
||||
@ -1228,6 +1339,53 @@ public class MessagingController implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upsync an entire message. This must also unwind whatever triggered it (either by
|
||||
* updating the serverId, or by deleting the update record, or it's going to keep happening
|
||||
* over and over again.
|
||||
*
|
||||
* Note: If the message is being uploaded into an unexpected mailbox, we *do not* upload.
|
||||
* This is to avoid unnecessary uploads into the trash. Although the caller attempts to select
|
||||
* only the Drafts and Sent folders, this can happen when the update record and the current
|
||||
* record mismatch. In this case, we let the update record remain, because the filters
|
||||
* in processPendingUpdatesSynchronous() will pick it up as a move and handle it (or drop it)
|
||||
* appropriately.
|
||||
*
|
||||
* @param resolver
|
||||
* @param remoteStore
|
||||
* @param account
|
||||
* @param mailbox the actual mailbox
|
||||
* @param messageId
|
||||
*/
|
||||
private void processUploadMessage(ContentResolver resolver, Store remoteStore,
|
||||
EmailContent.Account account, Mailbox mailbox, long messageId)
|
||||
throws MessagingException {
|
||||
EmailContent.Message message =
|
||||
EmailContent.Message.restoreMessageWithId(mContext, messageId);
|
||||
boolean deleteUpdate = false;
|
||||
if (message == null) {
|
||||
deleteUpdate = true;
|
||||
Log.d(Email.LOG_TAG, "Upsync failed for null message, id=" + messageId);
|
||||
} else if (mailbox.mType == Mailbox.TYPE_DRAFTS) {
|
||||
deleteUpdate = false;
|
||||
Log.d(Email.LOG_TAG, "Upsync skipped for mailbox=drafts, id=" + messageId);
|
||||
} else if (mailbox.mType == Mailbox.TYPE_OUTBOX) {
|
||||
deleteUpdate = false;
|
||||
Log.d(Email.LOG_TAG, "Upsync skipped for mailbox=outbox, id=" + messageId);
|
||||
} else if (mailbox.mType == Mailbox.TYPE_TRASH) {
|
||||
deleteUpdate = false;
|
||||
Log.d(Email.LOG_TAG, "Upsync skipped for mailbox=trash, id=" + messageId);
|
||||
} else {
|
||||
Log.d(Email.LOG_TAG, "Upsyc triggered for message id=" + messageId);
|
||||
deleteUpdate = processPendingAppend(remoteStore, account, mailbox, message);
|
||||
}
|
||||
if (deleteUpdate) {
|
||||
// Finally, delete the update (if any)
|
||||
Uri uri = ContentUris.withAppendedId(EmailContent.Message.UPDATED_CONTENT_URI, messageId);
|
||||
resolver.delete(uri, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upsync changes to read or flagged
|
||||
*
|
||||
@ -1239,6 +1397,19 @@ public class MessagingController implements Runnable {
|
||||
*/
|
||||
private void processPendingFlagChange(Store remoteStore, Mailbox mailbox, boolean changeRead,
|
||||
boolean changeFlagged, EmailContent.Message newMessage) throws MessagingException {
|
||||
|
||||
// 0. No remote update if the message is local-only
|
||||
if (newMessage.mServerId == null || newMessage.mServerId.equals("")
|
||||
|| newMessage.mServerId.startsWith(LOCAL_SERVERID_PREFIX)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. No remote update for DRAFTS or OUTBOX
|
||||
if (mailbox.mType == Mailbox.TYPE_DRAFTS || mailbox.mType == Mailbox.TYPE_OUTBOX) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Open the remote store & folder
|
||||
Folder remoteFolder = remoteStore.getFolder(mailbox.mDisplayName);
|
||||
if (!remoteFolder.exists()) {
|
||||
return;
|
||||
@ -1247,7 +1418,8 @@ public class MessagingController implements Runnable {
|
||||
if (remoteFolder.getMode() != OpenMode.READ_WRITE) {
|
||||
return;
|
||||
}
|
||||
// Finally, apply the changes to the message
|
||||
|
||||
// 3. Finally, apply the changes to the message
|
||||
Message remoteMessage = remoteFolder.getMessage(newMessage.mServerId);
|
||||
if (remoteMessage == null) {
|
||||
return;
|
||||
@ -1280,6 +1452,12 @@ public class MessagingController implements Runnable {
|
||||
EmailContent.Account account, Mailbox newMailbox, EmailContent.Message oldMessage,
|
||||
final EmailContent.Message newMessage) throws MessagingException {
|
||||
|
||||
// 0. No remote move if the message is local-only
|
||||
if (newMessage.mServerId == null || newMessage.mServerId.equals("")
|
||||
|| newMessage.mServerId.startsWith(LOCAL_SERVERID_PREFIX)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. Escape early if we can't find the local mailbox
|
||||
// TODO smaller projection here
|
||||
Mailbox oldMailbox = Mailbox.restoreMailboxWithId(mContext, oldMessage.mMailboxKey);
|
||||
@ -1427,96 +1605,131 @@ public class MessagingController implements Runnable {
|
||||
/**
|
||||
* Process a pending append message command. This command uploads a local message to the
|
||||
* server, first checking to be sure that the server message is not newer than
|
||||
* the local message. Once the local message is successfully processed it is deleted so
|
||||
* that the server message will be synchronized down without an additional copy being
|
||||
* created.
|
||||
* TODO update the local message UID instead of deleteing it
|
||||
* the local message.
|
||||
*
|
||||
* @param command arguments = (String folder, String uid)
|
||||
* @param account
|
||||
* @throws MessagingException
|
||||
* @param remoteStore the remote store we're working in
|
||||
* @param account The account in which we are working
|
||||
* @param newMailbox The mailbox we're appending to
|
||||
* @param message The message we're appending
|
||||
* @return true if successfully uploaded
|
||||
*/
|
||||
private void processPendingAppend(PendingCommand command, EmailContent.Account account)
|
||||
private boolean processPendingAppend(Store remoteStore, EmailContent.Account account,
|
||||
Mailbox newMailbox, EmailContent.Message message)
|
||||
throws MessagingException {
|
||||
String folder = command.arguments[0];
|
||||
String uid = command.arguments[1];
|
||||
|
||||
LocalStore localStore = (LocalStore) Store.getInstance(
|
||||
account.getLocalStoreUri(mContext), mContext, null);
|
||||
LocalFolder localFolder = (LocalFolder) localStore.getFolder(folder);
|
||||
LocalMessage localMessage = (LocalMessage) localFolder.getMessage(uid);
|
||||
boolean updateInternalDate = false;
|
||||
boolean updateMessage = false;
|
||||
boolean deleteMessage = false;
|
||||
|
||||
if (localMessage == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Store remoteStore = Store.getInstance(account.getStoreUri(mContext), mContext,
|
||||
localStore.getPersistentCallbacks());
|
||||
Folder remoteFolder = remoteStore.getFolder(folder);
|
||||
// 1. Find the remote folder that we're appending to and create and/or open it
|
||||
Folder remoteFolder = remoteStore.getFolder(newMailbox.mDisplayName);
|
||||
if (!remoteFolder.exists()) {
|
||||
if (!remoteFolder.canCreate(FolderType.HOLDS_MESSAGES)) {
|
||||
// This is POP3, we cannot actually upload. Instead, we'll update the message
|
||||
// locally with a fake serverId (so we don't keep trying here) and return.
|
||||
if (message.mServerId == null || message.mServerId.length() == 0) {
|
||||
message.mServerId = LOCAL_SERVERID_PREFIX + message.mId;
|
||||
Uri uri =
|
||||
ContentUris.withAppendedId(EmailContent.Message.CONTENT_URI, message.mId);
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(EmailContent.Message.SERVER_ID, message.mServerId);
|
||||
mContext.getContentResolver().update(uri, cv, null, null);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (!remoteFolder.create(FolderType.HOLDS_MESSAGES)) {
|
||||
return;
|
||||
// This is a (hopefully) transient error and we return false to try again later
|
||||
return false;
|
||||
}
|
||||
}
|
||||
remoteFolder.open(OpenMode.READ_WRITE, localFolder.getPersistentCallbacks());
|
||||
remoteFolder.open(OpenMode.READ_WRITE, null);
|
||||
if (remoteFolder.getMode() != OpenMode.READ_WRITE) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. If possible, load a remote message with the matching UID
|
||||
Message remoteMessage = null;
|
||||
if (!localMessage.getUid().startsWith("Local")
|
||||
&& !localMessage.getUid().contains("-")) {
|
||||
remoteMessage = remoteFolder.getMessage(localMessage.getUid());
|
||||
if (message.mServerId != null && message.mServerId.length() > 0) {
|
||||
remoteMessage = remoteFolder.getMessage(message.mServerId);
|
||||
}
|
||||
|
||||
// 3. If a remote message could not be found, upload our local message
|
||||
if (remoteMessage == null) {
|
||||
/*
|
||||
* If the message does not exist remotely we just upload it and then
|
||||
* update our local copy with the new uid.
|
||||
*/
|
||||
// 3a. Create a legacy message to upload
|
||||
Message localMessage = LegacyConversions.makeMessage(mContext, message);
|
||||
|
||||
// 3b. Upload it
|
||||
FetchProfile fp = new FetchProfile();
|
||||
fp.add(FetchProfile.Item.BODY);
|
||||
localFolder.fetch(new Message[] { localMessage }, fp, null);
|
||||
String oldUid = localMessage.getUid();
|
||||
remoteFolder.appendMessages(new Message[] { localMessage });
|
||||
localFolder.changeUid(localMessage);
|
||||
// mListeners.messageUidChanged(account.mId, -1 folder.mId, oldUid, localMessage.getUid());
|
||||
}
|
||||
else {
|
||||
/*
|
||||
* If the remote message exists we need to determine which copy to keep.
|
||||
*/
|
||||
/*
|
||||
* See if the remote message is newer than ours.
|
||||
*/
|
||||
|
||||
// 3b. And record the UID from the server
|
||||
message.mServerId = localMessage.getUid();
|
||||
updateInternalDate = true;
|
||||
updateMessage = true;
|
||||
} else {
|
||||
// 4. If the remote message exists we need to determine which copy to keep.
|
||||
FetchProfile fp = new FetchProfile();
|
||||
fp.add(FetchProfile.Item.ENVELOPE);
|
||||
remoteFolder.fetch(new Message[] { remoteMessage }, fp, null);
|
||||
Date localDate = localMessage.getInternalDate();
|
||||
Date localDate = new Date(message.mServerTimeStamp);
|
||||
Date remoteDate = remoteMessage.getInternalDate();
|
||||
if (remoteDate.compareTo(localDate) > 0) {
|
||||
/*
|
||||
* If the remote message is newer than ours we'll just
|
||||
* delete ours and move on. A sync will get the server message
|
||||
* if we need to be able to see it.
|
||||
*/
|
||||
localMessage.setFlag(Flag.DELETED, true);
|
||||
}
|
||||
else {
|
||||
/*
|
||||
* Otherwise we'll upload our message and then delete the remote message.
|
||||
*/
|
||||
// 4a. If the remote message is newer than ours we'll just
|
||||
// delete ours and move on. A sync will get the server message
|
||||
// if we need to be able to see it.
|
||||
deleteMessage = true;
|
||||
} else {
|
||||
// 4b. Otherwise we'll upload our message and then delete the remote message.
|
||||
|
||||
// Create a legacy message to upload
|
||||
Message localMessage = LegacyConversions.makeMessage(mContext, message);
|
||||
|
||||
// 4c. Upload it
|
||||
fp.clear();
|
||||
fp = new FetchProfile();
|
||||
fp.add(FetchProfile.Item.BODY);
|
||||
localFolder.fetch(new Message[] { localMessage }, fp, null);
|
||||
String oldUid = localMessage.getUid();
|
||||
remoteFolder.appendMessages(new Message[] { localMessage });
|
||||
localFolder.changeUid(localMessage);
|
||||
// mListeners.messageUidChanged(account.mId, folder.mId, oldUid, localMessage.getUid());
|
||||
|
||||
// 4d. Record the UID and new internalDate from the server
|
||||
message.mServerId = localMessage.getUid();
|
||||
updateInternalDate = true;
|
||||
updateMessage = true;
|
||||
|
||||
// 4e. And delete the old copy of the message from the server
|
||||
remoteMessage.setFlag(Flag.DELETED, true);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. If requested, Best-effort to capture new "internaldate" from the server
|
||||
if (updateInternalDate) {
|
||||
try {
|
||||
Message remoteMessage2 = remoteFolder.getMessage(message.mServerId);
|
||||
FetchProfile fp2 = new FetchProfile();
|
||||
fp2.add(FetchProfile.Item.ENVELOPE);
|
||||
remoteFolder.fetch(new Message[] { remoteMessage2 }, fp2, null);
|
||||
message.mServerTimeStamp = remoteMessage2.getInternalDate().getTime();
|
||||
updateMessage = true;
|
||||
} catch (MessagingException me) {
|
||||
// skip it - we can live without this
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Perform required edits to local copy of message
|
||||
if (deleteMessage || updateMessage) {
|
||||
Uri uri = ContentUris.withAppendedId(EmailContent.Message.CONTENT_URI, message.mId);
|
||||
ContentResolver resolver = mContext.getContentResolver();
|
||||
if (deleteMessage) {
|
||||
resolver.delete(uri, null, null);
|
||||
} else if (updateMessage) {
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(EmailContent.Message.SERVER_ID, message.mServerId);
|
||||
cv.put(EmailContent.Message.SERVER_TIMESTAMP, message.mServerTimeStamp);
|
||||
resolver.update(uri, cv, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1791,13 +2004,16 @@ public class MessagingController implements Runnable {
|
||||
continue;
|
||||
}
|
||||
// 5. move to sent, or delete
|
||||
Uri uri = ContentUris.withAppendedId(EmailContent.Message.CONTENT_URI, messageId);
|
||||
Uri syncedUri =
|
||||
ContentUris.withAppendedId(EmailContent.Message.SYNCED_CONTENT_URI, messageId);
|
||||
if (requireMoveMessageToSentFolder) {
|
||||
resolver.update(uri, moveToSentValues, null, null);
|
||||
// TODO: post for a pending upload
|
||||
resolver.update(syncedUri, moveToSentValues, null, null);
|
||||
} else {
|
||||
AttachmentProvider.deleteAllAttachmentFiles(mContext, account.mId, messageId);
|
||||
Uri uri =
|
||||
ContentUris.withAppendedId(EmailContent.Message.CONTENT_URI, messageId);
|
||||
resolver.delete(uri, null, null);
|
||||
resolver.delete(syncedUri, null, null);
|
||||
}
|
||||
}
|
||||
// 6. report completion/success
|
||||
@ -1859,34 +2075,6 @@ public class MessagingController implements Runnable {
|
||||
});
|
||||
}
|
||||
|
||||
public void saveDraft(final EmailContent.Account account, final Message message) {
|
||||
// TODO rewrite using provider upates
|
||||
|
||||
// try {
|
||||
// Store localStore = Store.getInstance(account.getLocalStoreUri(mContext), mContext,
|
||||
// null);
|
||||
// LocalFolder localFolder =
|
||||
// (LocalFolder) localStore.getFolder(account.getDraftsFolderName(mContext));
|
||||
// localFolder.open(OpenMode.READ_WRITE, null);
|
||||
// localFolder.appendMessages(new Message[] {
|
||||
// message
|
||||
// });
|
||||
// Message localMessage = localFolder.getMessage(message.getUid());
|
||||
// localMessage.setFlag(Flag.X_DOWNLOADED_FULL, true);
|
||||
//
|
||||
// PendingCommand command = new PendingCommand();
|
||||
// command.command = PENDING_COMMAND_APPEND;
|
||||
// command.arguments = new String[] {
|
||||
// localFolder.getName(),
|
||||
// localMessage.getUid() };
|
||||
// queuePendingCommand(account, command);
|
||||
// processPendingCommands(account);
|
||||
// }
|
||||
// catch (MessagingException e) {
|
||||
// Log.e(Email.LOG_TAG, "Unable to save message as draft.", e);
|
||||
// }
|
||||
}
|
||||
|
||||
private static class Command {
|
||||
public Runnable runnable;
|
||||
|
||||
|
@ -36,6 +36,7 @@ import com.android.email.provider.EmailContent.MessageColumns;
|
||||
import android.app.Activity;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@ -529,9 +530,9 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
|
||||
// the reply fields are only filled/used for Drafts.
|
||||
if (isEditDraft) {
|
||||
message.mHtmlReply =
|
||||
Body.restoreHtmlReplyWithMessageId(MessageCompose.this, message.mId);
|
||||
Body.restoreReplyHtmlWithMessageId(MessageCompose.this, message.mId);
|
||||
message.mTextReply =
|
||||
Body.restoreTextReplyWithMessageId(MessageCompose.this, message.mId);
|
||||
Body.restoreReplyTextWithMessageId(MessageCompose.this, message.mId);
|
||||
} else {
|
||||
message.mHtmlReply = null;
|
||||
message.mTextReply = null;
|
||||
@ -749,7 +750,12 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
|
||||
protected Void doInBackground(Void... params) {
|
||||
synchronized (mDraft) {
|
||||
if (mDraft.isSaved()) {
|
||||
mDraft.update(MessageCompose.this, getUpdateContentValues(mDraft));
|
||||
// Update the message
|
||||
Uri draftUri =
|
||||
ContentUris.withAppendedId(mDraft.SYNCED_CONTENT_URI, mDraft.mId);
|
||||
getContentResolver().update(draftUri, getUpdateContentValues(mDraft),
|
||||
null, null);
|
||||
// Update the body
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(BodyColumns.TEXT_CONTENT, mDraft.mText);
|
||||
values.put(BodyColumns.TEXT_REPLY, mDraft.mTextReply);
|
||||
|
@ -780,7 +780,7 @@ public class MessageList extends ListActivity implements OnItemClickListener, On
|
||||
*
|
||||
* Here are some rules (finish this list):
|
||||
*
|
||||
* Any merged box (except send): refresh
|
||||
* Any merged, synced box (except send): refresh
|
||||
* Any push-mode account: refresh
|
||||
* Any non-push-mode account: load more
|
||||
* Any outbox (send again):
|
||||
@ -791,11 +791,14 @@ public class MessageList extends ListActivity implements OnItemClickListener, On
|
||||
// first, look for shortcuts that don't need us to spin up a DB access task
|
||||
if (mailboxId == Mailbox.QUERY_ALL_INBOXES
|
||||
|| mailboxId == Mailbox.QUERY_ALL_UNREAD
|
||||
|| mailboxId == Mailbox.QUERY_ALL_FAVORITES
|
||||
|| mailboxId == Mailbox.QUERY_ALL_DRAFTS) {
|
||||
|| mailboxId == Mailbox.QUERY_ALL_FAVORITES) {
|
||||
finishFooterView(LIST_FOOTER_MODE_REFRESH);
|
||||
return;
|
||||
}
|
||||
if (mailboxId == Mailbox.QUERY_ALL_DRAFTS || mailboxType == Mailbox.TYPE_DRAFTS) {
|
||||
finishFooterView(LIST_FOOTER_MODE_NONE);
|
||||
return;
|
||||
}
|
||||
if (mailboxId == Mailbox.QUERY_ALL_OUTBOX || mailboxType == Mailbox.TYPE_OUTBOX) {
|
||||
finishFooterView(LIST_FOOTER_MODE_SEND);
|
||||
return;
|
||||
@ -839,8 +842,11 @@ public class MessageList extends ListActivity implements OnItemClickListener, On
|
||||
return LIST_FOOTER_MODE_NONE;
|
||||
}
|
||||
}
|
||||
if (mailboxType == Mailbox.TYPE_OUTBOX) {
|
||||
return LIST_FOOTER_MODE_SEND;
|
||||
switch (mailboxType) {
|
||||
case Mailbox.TYPE_OUTBOX:
|
||||
return LIST_FOOTER_MODE_SEND;
|
||||
case Mailbox.TYPE_DRAFTS:
|
||||
return LIST_FOOTER_MODE_NONE;
|
||||
}
|
||||
if (accountId != -1) {
|
||||
// This is inefficient but the best fix is not here but in isMessagingController
|
||||
|
@ -74,6 +74,19 @@ public abstract class Folder {
|
||||
*/
|
||||
public abstract OpenMode getMode() throws MessagingException;
|
||||
|
||||
/**
|
||||
* Reports if the Store is able to create folders of the given type.
|
||||
* Does not actually attempt to create a folder.
|
||||
* @param type
|
||||
* @return true if can create, false if cannot create
|
||||
*/
|
||||
public abstract boolean canCreate(FolderType type);
|
||||
|
||||
/**
|
||||
* Attempt to create the given folder remotely using the given type.
|
||||
* @param type
|
||||
* @return true if created, false if cannot create (e.g. server side)
|
||||
*/
|
||||
public abstract boolean create(FolderType type) throws MessagingException;
|
||||
|
||||
public abstract boolean exists() throws MessagingException;
|
||||
|
@ -16,15 +16,14 @@
|
||||
|
||||
package com.android.email.mail.internet;
|
||||
|
||||
import com.android.email.Utility;
|
||||
import com.android.email.mail.MessagingException;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.android.email.Utility;
|
||||
import com.android.email.mail.MessagingException;
|
||||
|
||||
public class MimeHeader {
|
||||
/**
|
||||
@ -35,6 +34,10 @@ public class MimeHeader {
|
||||
* into the MIME data by LocalStore.fetch.
|
||||
*/
|
||||
public static final String HEADER_ANDROID_ATTACHMENT_STORE_DATA = "X-Android-Attachment-StoreData";
|
||||
/**
|
||||
* Application specific header that is used to tag body parts for quoted/forwarded messages.
|
||||
*/
|
||||
public static final String HEADER_ANDROID_BODY_QUOTED_PART = "X-Android-Body-Quoted-Part";
|
||||
|
||||
public static final String HEADER_CONTENT_TYPE = "Content-Type";
|
||||
public static final String HEADER_CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding";
|
||||
@ -143,4 +146,9 @@ public class MimeHeader {
|
||||
return name + "=" + value;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return (mFields == null) ? null : mFields.toString();
|
||||
}
|
||||
}
|
||||
|
@ -447,6 +447,11 @@ public class ImapStore extends Store {
|
||||
}
|
||||
}
|
||||
|
||||
// IMAP supports folder creation
|
||||
public boolean canCreate(FolderType type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean create(FolderType type) throws MessagingException {
|
||||
/*
|
||||
* This method needs to operate in the unselected mode as well as the selected mode
|
||||
@ -1008,7 +1013,8 @@ public class ImapStore extends Store {
|
||||
|
||||
/*
|
||||
* Try to find the UID of the message we just appended using the
|
||||
* Message-ID header.
|
||||
* Message-ID header. If there are more than one response, take the
|
||||
* last one, as it's most likely he newest (the one we just uploaded).
|
||||
*/
|
||||
String[] messageIdHeader = message.getHeader("Message-ID");
|
||||
if (messageIdHeader == null || messageIdHeader.length == 0) {
|
||||
@ -1021,7 +1027,7 @@ public class ImapStore extends Store {
|
||||
for (ImapResponse response1 : responses) {
|
||||
if (response1.mTag == null && response1.get(0).equals("SEARCH")
|
||||
&& response1.size() > 1) {
|
||||
message.setUid(response1.getString(1));
|
||||
message.setUid(response1.getString(response1.size()-1));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,6 @@ import com.android.email.mail.internet.MimeMessage;
|
||||
import com.android.email.mail.internet.MimeMultipart;
|
||||
import com.android.email.mail.internet.MimeUtility;
|
||||
import com.android.email.mail.internet.TextBody;
|
||||
import com.android.email.provider.AttachmentProvider;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
@ -46,7 +45,6 @@ import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.net.Uri;
|
||||
import android.util.Config;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
@ -629,6 +627,12 @@ public class LocalStore extends Store implements PersistentDataCallbacks {
|
||||
return Utility.arrayContains(getPersonalNamespaces(), this);
|
||||
}
|
||||
|
||||
// LocalStore supports folder creation
|
||||
@Override
|
||||
public boolean canCreate(FolderType type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean create(FolderType type) throws MessagingException {
|
||||
if (exists()) {
|
||||
|
@ -29,7 +29,6 @@ import com.android.email.mail.Store;
|
||||
import com.android.email.mail.Transport;
|
||||
import com.android.email.mail.Folder.OpenMode;
|
||||
import com.android.email.mail.internet.MimeMessage;
|
||||
import com.android.email.mail.store.ImapStore.ImapMessage;
|
||||
import com.android.email.mail.transport.LoggingInputStream;
|
||||
import com.android.email.mail.transport.MailTransport;
|
||||
|
||||
@ -332,6 +331,11 @@ public class Pop3Store extends Store {
|
||||
return mName;
|
||||
}
|
||||
|
||||
// POP3 does not folder creation
|
||||
public boolean canCreate(FolderType type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean create(FolderType type) throws MessagingException {
|
||||
return false;
|
||||
|
@ -195,7 +195,6 @@ public abstract class EmailContent {
|
||||
public static final String TABLE_NAME = "Body";
|
||||
public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/body");
|
||||
|
||||
|
||||
public static final int CONTENT_ID_COLUMN = 0;
|
||||
public static final int CONTENT_MESSAGE_KEY_COLUMN = 1;
|
||||
public static final int CONTENT_HTML_CONTENT_COLUMN = 2;
|
||||
@ -210,23 +209,22 @@ public abstract class EmailContent {
|
||||
BodyColumns.INTRO_TEXT
|
||||
};
|
||||
|
||||
public static final String[] TEXT_PROJECTION = new String[] {
|
||||
public static final String[] COMMON_PROJECTION_TEXT = new String[] {
|
||||
RECORD_ID, BodyColumns.TEXT_CONTENT
|
||||
};
|
||||
|
||||
public static final String[] HTML_PROJECTION = new String[] {
|
||||
public static final String[] COMMON_PROJECTION_HTML = new String[] {
|
||||
RECORD_ID, BodyColumns.HTML_CONTENT
|
||||
};
|
||||
|
||||
public static final String[] HTML_REPLY_PROJECTION = new String[] {
|
||||
RECORD_ID, BodyColumns.HTML_REPLY
|
||||
};
|
||||
|
||||
public static final String[] TEXT_REPLY_PROJECTION = new String[] {
|
||||
public static final String[] COMMON_PROJECTION_REPLY_TEXT = new String[] {
|
||||
RECORD_ID, BodyColumns.TEXT_REPLY
|
||||
};
|
||||
|
||||
public static final int COMMON_TEXT_COLUMN = 1;
|
||||
public static final String[] COMMON_PROJECTION_REPLY_HTML = new String[] {
|
||||
RECORD_ID, BodyColumns.HTML_REPLY
|
||||
};
|
||||
public static final String[] COMMON_PROJECTION_INTRO = new String[] {
|
||||
RECORD_ID, BodyColumns.INTRO_TEXT
|
||||
};
|
||||
public static final int COMMON_PROJECTION_COLUMN_TEXT = 1;
|
||||
|
||||
public long mMessageKey;
|
||||
public String mHtmlContent;
|
||||
@ -240,7 +238,7 @@ public abstract class EmailContent {
|
||||
mBaseUri = CONTENT_URI;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Override
|
||||
public ContentValues toContentValues() {
|
||||
ContentValues values = new ContentValues();
|
||||
|
||||
@ -255,39 +253,39 @@ public abstract class EmailContent {
|
||||
return values;
|
||||
}
|
||||
|
||||
private static Body restoreBodyWithCursor(Cursor cursor) {
|
||||
try {
|
||||
if (cursor.moveToFirst()) {
|
||||
return getContent(cursor, Body.class);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
private static Body restoreBodyWithCursor(Cursor cursor) {
|
||||
try {
|
||||
if (cursor.moveToFirst()) {
|
||||
return getContent(cursor, Body.class);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
public static Body restoreBodyWithId(Context context, long id) {
|
||||
Uri u = ContentUris.withAppendedId(Body.CONTENT_URI, id);
|
||||
Cursor c = context.getContentResolver().query(u, Body.CONTENT_PROJECTION,
|
||||
null, null, null);
|
||||
return restoreBodyWithCursor(c);
|
||||
}
|
||||
public static Body restoreBodyWithId(Context context, long id) {
|
||||
Uri u = ContentUris.withAppendedId(Body.CONTENT_URI, id);
|
||||
Cursor c = context.getContentResolver().query(u, Body.CONTENT_PROJECTION,
|
||||
null, null, null);
|
||||
return restoreBodyWithCursor(c);
|
||||
}
|
||||
|
||||
public static Body restoreBodyWithMessageId(Context context, long messageId) {
|
||||
Cursor c = context.getContentResolver().query(Body.CONTENT_URI,
|
||||
Body.CONTENT_PROJECTION, Body.MESSAGE_KEY + "=?",
|
||||
new String[] {Long.toString(messageId)}, null);
|
||||
return restoreBodyWithCursor(c);
|
||||
}
|
||||
public static Body restoreBodyWithMessageId(Context context, long messageId) {
|
||||
Cursor c = context.getContentResolver().query(Body.CONTENT_URI,
|
||||
Body.CONTENT_PROJECTION, Body.MESSAGE_KEY + "=?",
|
||||
new String[] {Long.toString(messageId)}, null);
|
||||
return restoreBodyWithCursor(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bodyId for the given messageId, or -1 if no body is found.
|
||||
*/
|
||||
public static long lookupBodyIdWithMessageId(ContentResolver resolver, long messageId) {
|
||||
Cursor c = resolver.query(Body.CONTENT_URI, ID_PROJECTION,
|
||||
Body.MESSAGE_KEY + "=?",
|
||||
new String[] {Long.toString(messageId)}, null);
|
||||
Body.MESSAGE_KEY + "=?",
|
||||
new String[] {Long.toString(messageId)}, null);
|
||||
try {
|
||||
return c.moveToFirst() ? c.getLong(ID_PROJECTION_COLUMN) : -1;
|
||||
} finally {
|
||||
@ -313,13 +311,13 @@ public abstract class EmailContent {
|
||||
}
|
||||
}
|
||||
|
||||
private static String restoreTextWithMessageId(Context context, long messageId,
|
||||
String[] projection) {
|
||||
private static String restoreTextWithMessageId(Context context, long messageId,
|
||||
String[] projection) {
|
||||
Cursor c = context.getContentResolver().query(Body.CONTENT_URI, projection,
|
||||
Body.MESSAGE_KEY + "=?", new String[] {Long.toString(messageId)}, null);
|
||||
try {
|
||||
if (c.moveToFirst()) {
|
||||
return c.getString(COMMON_TEXT_COLUMN);
|
||||
return c.getString(COMMON_PROJECTION_COLUMN_TEXT);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
@ -329,19 +327,23 @@ public abstract class EmailContent {
|
||||
}
|
||||
|
||||
public static String restoreBodyTextWithMessageId(Context context, long messageId) {
|
||||
return restoreTextWithMessageId(context, messageId, Body.TEXT_PROJECTION);
|
||||
return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_TEXT);
|
||||
}
|
||||
|
||||
public static String restoreBodyHtmlWithMessageId(Context context, long messageId) {
|
||||
return restoreTextWithMessageId(context, messageId, Body.HTML_PROJECTION);
|
||||
return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_HTML);
|
||||
}
|
||||
|
||||
public static String restoreTextReplyWithMessageId(Context context, long messageId) {
|
||||
return restoreTextWithMessageId(context, messageId, Body.TEXT_REPLY_PROJECTION);
|
||||
public static String restoreReplyTextWithMessageId(Context context, long messageId) {
|
||||
return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_REPLY_TEXT);
|
||||
}
|
||||
|
||||
public static String restoreHtmlReplyWithMessageId(Context context, long messageId) {
|
||||
return restoreTextWithMessageId(context, messageId, Body.HTML_REPLY_PROJECTION);
|
||||
public static String restoreReplyHtmlWithMessageId(Context context, long messageId) {
|
||||
return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_REPLY_HTML);
|
||||
}
|
||||
|
||||
public static String restoreIntroTextWithMessageId(Context context, long messageId) {
|
||||
return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_INTRO);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -485,6 +487,12 @@ public abstract class EmailContent {
|
||||
RECORD_ID, SyncColumns.SERVER_ID
|
||||
};
|
||||
|
||||
public static final int ID_MAILBOX_COLUMN_ID = 0;
|
||||
public static final int ID_MAILBOX_COLUMN_MAILBOX_KEY = 1;
|
||||
public static final String[] ID_MAILBOX_PROJECTION = new String[] {
|
||||
RECORD_ID, MessageColumns.MAILBOX_KEY
|
||||
};
|
||||
|
||||
public static final String[] ID_COLUMN_PROJECTION = new String[] { RECORD_ID };
|
||||
|
||||
// _id field is in AbstractContent
|
||||
|
@ -16,14 +16,18 @@
|
||||
|
||||
package com.android.email;
|
||||
|
||||
import com.android.email.mail.Address;
|
||||
import com.android.email.mail.BodyPart;
|
||||
import com.android.email.mail.Flag;
|
||||
import com.android.email.mail.Message;
|
||||
import com.android.email.mail.MessageTestUtils;
|
||||
import com.android.email.mail.MessagingException;
|
||||
import com.android.email.mail.Part;
|
||||
import com.android.email.mail.Message.RecipientType;
|
||||
import com.android.email.mail.MessageTestUtils.MessageBuilder;
|
||||
import com.android.email.mail.MessageTestUtils.MultipartBuilder;
|
||||
import com.android.email.mail.internet.MimeHeader;
|
||||
import com.android.email.mail.internet.MimeMessage;
|
||||
import com.android.email.mail.internet.MimeUtility;
|
||||
import com.android.email.provider.EmailContent;
|
||||
import com.android.email.provider.EmailProvider;
|
||||
@ -170,4 +174,117 @@ public class LegacyConversionsTests extends ProviderTestCase2<EmailProvider> {
|
||||
* TODO: Sunny day test of adding attachments from a POP message.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sunny day tests of converting an original message to a legacy message
|
||||
*/
|
||||
public void testMakeLegacyMessage() throws MessagingException {
|
||||
// Set up and store a message in the provider
|
||||
long account1Id = 1;
|
||||
long mailbox1Id = 1;
|
||||
|
||||
// Test message 1: No body
|
||||
EmailContent.Message localMessage1 = ProviderTestUtils.setupMessage("make-legacy",
|
||||
account1Id, mailbox1Id, false, true, mProviderContext);
|
||||
Message getMessage1 = LegacyConversions.makeMessage(mProviderContext, localMessage1);
|
||||
checkLegacyMessage("no body", localMessage1, getMessage1);
|
||||
|
||||
// Test message 2: Simple body
|
||||
EmailContent.Message localMessage2 = ProviderTestUtils.setupMessage("make-legacy",
|
||||
account1Id, mailbox1Id, true, false, mProviderContext);
|
||||
localMessage2.mTextReply = null;
|
||||
localMessage2.mHtmlReply = null;
|
||||
localMessage2.mIntroText = null;
|
||||
localMessage2.mFlags &= ~EmailContent.Message.FLAG_TYPE_MASK;
|
||||
localMessage2.save(mProviderContext);
|
||||
Message getMessage2 = LegacyConversions.makeMessage(mProviderContext, localMessage2);
|
||||
checkLegacyMessage("simple body", localMessage2, getMessage2);
|
||||
|
||||
// Test message 3: Body + replied-to text
|
||||
EmailContent.Message localMessage3 = ProviderTestUtils.setupMessage("make-legacy",
|
||||
account1Id, mailbox1Id, true, false, mProviderContext);
|
||||
localMessage3.mFlags &= ~EmailContent.Message.FLAG_TYPE_MASK;
|
||||
localMessage3.mFlags |= EmailContent.Message.FLAG_TYPE_REPLY;
|
||||
localMessage3.save(mProviderContext);
|
||||
Message getMessage3 = LegacyConversions.makeMessage(mProviderContext, localMessage3);
|
||||
checkLegacyMessage("reply-to", localMessage3, getMessage3);
|
||||
|
||||
// Test message 4: Body + forwarded text
|
||||
EmailContent.Message localMessage4 = ProviderTestUtils.setupMessage("make-legacy",
|
||||
account1Id, mailbox1Id, true, false, mProviderContext);
|
||||
localMessage4.mFlags &= ~EmailContent.Message.FLAG_TYPE_MASK;
|
||||
localMessage4.mFlags |= EmailContent.Message.FLAG_TYPE_FORWARD;
|
||||
localMessage4.save(mProviderContext);
|
||||
Message getMessage4 = LegacyConversions.makeMessage(mProviderContext, localMessage4);
|
||||
checkLegacyMessage("forwarding", localMessage4, getMessage4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check equality of a pair of converted message
|
||||
*/
|
||||
private void checkLegacyMessage(String tag, EmailContent.Message expect, Message actual)
|
||||
throws MessagingException {
|
||||
assertEquals(tag, expect.mServerId, actual.getUid());
|
||||
assertEquals(tag, expect.mSubject, actual.getSubject());
|
||||
assertEquals(tag, expect.mFrom, Address.pack(actual.getFrom()));
|
||||
assertEquals(tag, expect.mTimeStamp, actual.getSentDate().getTime());
|
||||
assertEquals(tag, expect.mTo, Address.pack(actual.getRecipients(RecipientType.TO)));
|
||||
assertEquals(tag, expect.mCc, Address.pack(actual.getRecipients(RecipientType.CC)));
|
||||
assertEquals(tag, expect.mMessageId, ((MimeMessage)actual).getMessageId());
|
||||
// check flags
|
||||
assertEquals(tag, expect.mFlagRead, actual.isSet(Flag.SEEN));
|
||||
assertEquals(tag, expect.mFlagFavorite, actual.isSet(Flag.FLAGGED));
|
||||
|
||||
// Check the body of the message
|
||||
ArrayList<Part> viewables = new ArrayList<Part>();
|
||||
ArrayList<Part> attachments = new ArrayList<Part>();
|
||||
MimeUtility.collectParts(actual, viewables, attachments);
|
||||
String get1Text = null;
|
||||
String get1Html = null;
|
||||
String get1TextReply = null;
|
||||
String get1HtmlReply = null;
|
||||
String get1TextIntro = null;
|
||||
for (Part viewable : viewables) {
|
||||
String text = MimeUtility.getTextFromPart(viewable);
|
||||
boolean isHtml = viewable.getMimeType().equalsIgnoreCase("text/html");
|
||||
String[] headers = viewable.getHeader(MimeHeader.HEADER_ANDROID_BODY_QUOTED_PART);
|
||||
if (headers != null) {
|
||||
String header = headers[0];
|
||||
boolean isReply = LegacyConversions.BODY_QUOTED_PART_REPLY.equalsIgnoreCase(header);
|
||||
boolean isFwd = LegacyConversions.BODY_QUOTED_PART_FORWARD.equalsIgnoreCase(header);
|
||||
boolean isIntro = LegacyConversions.BODY_QUOTED_PART_INTRO.equalsIgnoreCase(header);
|
||||
if (isReply || isFwd) {
|
||||
if (isHtml) {
|
||||
get1HtmlReply = text;
|
||||
} else {
|
||||
get1TextReply = text;
|
||||
}
|
||||
} else if (isIntro) {
|
||||
get1TextIntro = text;
|
||||
}
|
||||
// Check flags
|
||||
int replyTypeFlags = expect.mFlags & EmailContent.Message.FLAG_TYPE_MASK;
|
||||
if (isReply) {
|
||||
assertEquals(tag, EmailContent.Message.FLAG_TYPE_REPLY, replyTypeFlags);
|
||||
}
|
||||
if (isFwd) {
|
||||
assertEquals(tag, EmailContent.Message.FLAG_TYPE_FORWARD, replyTypeFlags);
|
||||
}
|
||||
} else {
|
||||
if (isHtml) {
|
||||
get1Html = text;
|
||||
} else {
|
||||
get1Text = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
assertEquals(tag, expect.mText, get1Text);
|
||||
assertEquals(tag, expect.mHtml, get1Html);
|
||||
assertEquals(tag, expect.mTextReply, get1TextReply);
|
||||
assertEquals(tag, expect.mHtmlReply, get1HtmlReply);
|
||||
assertEquals(tag, expect.mIntroText, get1TextIntro);
|
||||
|
||||
// TODO Check the attachments
|
||||
|
||||
// cv.put("attachment_count", attachments.size());
|
||||
}
|
||||
}
|
||||
|
@ -19,132 +19,106 @@ package com.android.email.mail;
|
||||
public class MockFolder extends Folder {
|
||||
|
||||
@Override
|
||||
public void appendMessages(Message[] messages) throws MessagingException {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
public void appendMessages(Message[] messages) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close(boolean expunge) throws MessagingException {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
public void close(boolean expunge) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyMessages(Message[] msgs, Folder folder,
|
||||
MessageUpdateCallbacks callbacks) throws MessagingException {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
MessageUpdateCallbacks callbacks) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean create(FolderType type) throws MessagingException {
|
||||
// TODO Auto-generated method stub
|
||||
public boolean canCreate(FolderType type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(boolean recurse) throws MessagingException {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() throws MessagingException {
|
||||
// TODO Auto-generated method stub
|
||||
public boolean create(FolderType type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message[] expunge() throws MessagingException {
|
||||
// TODO Auto-generated method stub
|
||||
public void delete(boolean recurse) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message[] expunge() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetch(Message[] messages, FetchProfile fp, MessageRetrievalListener listener)
|
||||
throws MessagingException {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
public void fetch(Message[] messages, FetchProfile fp, MessageRetrievalListener listener) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message getMessage(String uid) throws MessagingException {
|
||||
// TODO Auto-generated method stub
|
||||
public Message getMessage(String uid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMessageCount() throws MessagingException {
|
||||
// TODO Auto-generated method stub
|
||||
public int getMessageCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message[] getMessages(int start, int end, MessageRetrievalListener listener)
|
||||
throws MessagingException {
|
||||
// TODO Auto-generated method stub
|
||||
public Message[] getMessages(int start, int end, MessageRetrievalListener listener) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message[] getMessages(MessageRetrievalListener listener) throws MessagingException {
|
||||
// TODO Auto-generated method stub
|
||||
public Message[] getMessages(MessageRetrievalListener listener) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message[] getMessages(String[] uids, MessageRetrievalListener listener)
|
||||
throws MessagingException {
|
||||
// TODO Auto-generated method stub
|
||||
public Message[] getMessages(String[] uids, MessageRetrievalListener listener) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OpenMode getMode() throws MessagingException {
|
||||
// TODO Auto-generated method stub
|
||||
public OpenMode getMode() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flag[] getPermanentFlags() throws MessagingException {
|
||||
// TODO Auto-generated method stub
|
||||
public Flag[] getPermanentFlags() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUnreadMessageCount() throws MessagingException {
|
||||
// TODO Auto-generated method stub
|
||||
public int getUnreadMessageCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
// TODO Auto-generated method stub
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open(OpenMode mode, PersistentDataCallbacks callbacks) throws MessagingException {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
public void open(OpenMode mode, PersistentDataCallbacks callbacks) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFlags(Message[] messages, Flag[] flags, boolean value) throws MessagingException {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
public void setFlags(Message[] messages, Flag[] flags, boolean value) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message createMessage(String uid) throws MessagingException {
|
||||
// TODO Auto-generated method stub
|
||||
public Message createMessage(String uid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@ import com.android.email.mail.Flag;
|
||||
import com.android.email.mail.Folder;
|
||||
import com.android.email.mail.MessagingException;
|
||||
import com.android.email.mail.Transport;
|
||||
import com.android.email.mail.Folder.FolderType;
|
||||
import com.android.email.mail.Folder.OpenMode;
|
||||
import com.android.email.mail.internet.BinaryTempFileBody;
|
||||
import com.android.email.mail.transport.MockTransport;
|
||||
@ -128,6 +129,10 @@ public class ImapStoreUnitTests extends AndroidTestCase {
|
||||
assertEquals(Flag.DELETED, flags[0]);
|
||||
assertEquals(Flag.SEEN, flags[1]);
|
||||
assertEquals(Flag.FLAGGED, flags[2]);
|
||||
|
||||
// canCreate() returns true
|
||||
assertTrue(mFolder.canCreate(FolderType.HOLDS_FOLDERS));
|
||||
assertTrue(mFolder.canCreate(FolderType.HOLDS_MESSAGES));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -236,11 +236,13 @@ public class Pop3StoreUnitTests extends AndroidTestCase {
|
||||
|
||||
// getMode() returns OpenMode.READ_WRITE
|
||||
assertEquals(OpenMode.READ_WRITE, mFolder.getMode());
|
||||
|
||||
// create() return false
|
||||
|
||||
// canCreate() && create() return false
|
||||
assertFalse(mFolder.canCreate(FolderType.HOLDS_FOLDERS));
|
||||
assertFalse(mFolder.canCreate(FolderType.HOLDS_MESSAGES));
|
||||
assertFalse(mFolder.create(FolderType.HOLDS_FOLDERS));
|
||||
assertFalse(mFolder.create(FolderType.HOLDS_MESSAGES));
|
||||
|
||||
|
||||
// getUnreadMessageCount() always returns -1
|
||||
assertEquals(-1, mFolder.getUnreadMessageCount());
|
||||
|
||||
|
@ -129,7 +129,7 @@ public class ProviderTestUtils extends Assert {
|
||||
message.mFlagLoaded = Message.FLAG_LOADED_UNLOADED;
|
||||
message.mFlagFavorite = true;
|
||||
message.mFlagAttachment = true;
|
||||
message.mFlags = 2;
|
||||
message.mFlags = 0;
|
||||
|
||||
message.mServerId = "serverid " + name;
|
||||
message.mServerTimeStamp = 300 + name.length();
|
||||
|
@ -479,6 +479,28 @@ public class ProviderTests extends ProviderTestCase2<EmailProvider> {
|
||||
assertEquals(body2.mIntroText, introText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test body retrieve methods
|
||||
*/
|
||||
public void testBodyRetrieve() {
|
||||
// No account needed
|
||||
// No mailbox needed
|
||||
Message message1 = ProviderTestUtils.setupMessage("bodyretrieve", 1, 1, true,
|
||||
true, mMockContext);
|
||||
long messageId = message1.mId;
|
||||
|
||||
assertEquals(message1.mText,
|
||||
Body.restoreBodyTextWithMessageId(mMockContext, messageId));
|
||||
assertEquals(message1.mHtml,
|
||||
Body.restoreBodyHtmlWithMessageId(mMockContext, messageId));
|
||||
assertEquals(message1.mTextReply,
|
||||
Body.restoreReplyTextWithMessageId(mMockContext, messageId));
|
||||
assertEquals(message1.mHtmlReply,
|
||||
Body.restoreReplyHtmlWithMessageId(mMockContext, messageId));
|
||||
assertEquals(message1.mIntroText,
|
||||
Body.restoreIntroTextWithMessageId(mMockContext, messageId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test delete body.
|
||||
* 1. create message without body (message id 1)
|
||||
|
Loading…
Reference in New Issue
Block a user