Add code to handle IMAP/POP attachments.
IMAP messages up to about 25k will be downloaded properly. * Move Store->Provider message rewrite code to a separate utility. * Add code to descend a Store message and write provider attachments. * Unit test basic IMAP attachment handler TODO: * handle large IMAP messages. * unit test for POP * unit test for large IMAP messages
This commit is contained in:
parent
a5abab243b
commit
3f1ac4da94
@ -435,10 +435,22 @@ public class Controller {
|
||||
public void loadAttachment(long attachmentId, long messageId, long accountId,
|
||||
final Result callback) {
|
||||
|
||||
Attachment attachInfo = Attachment.restoreAttachmentWithId(mProviderContext, attachmentId);
|
||||
|
||||
File saveToFile = AttachmentProvider.getAttachmentFilename(mContext,
|
||||
accountId, attachmentId);
|
||||
if (saveToFile.exists()) {
|
||||
// The attachment has already been downloaded, so we will just "pretend" to download it
|
||||
synchronized (mListeners) {
|
||||
for (Result listener : mListeners) {
|
||||
listener.loadAttachmentCallback(null, messageId, attachmentId, 0);
|
||||
}
|
||||
for (Result listener : mListeners) {
|
||||
listener.loadAttachmentCallback(null, messageId, attachmentId, 100);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Attachment attachInfo = Attachment.restoreAttachmentWithId(mProviderContext, attachmentId);
|
||||
|
||||
// Split here for target type (Service or MessagingController)
|
||||
IEmailService service = getServiceForMessage(messageId);
|
||||
|
275
src/com/android/email/LegacyConversions.java
Normal file
275
src/com/android/email/LegacyConversions.java
Normal file
@ -0,0 +1,275 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.email;
|
||||
|
||||
import com.android.email.mail.Address;
|
||||
import com.android.email.mail.Message;
|
||||
import com.android.email.mail.MessagingException;
|
||||
import com.android.email.mail.Part;
|
||||
import com.android.email.mail.internet.MimeHeader;
|
||||
import com.android.email.mail.internet.MimeUtility;
|
||||
import com.android.email.provider.AttachmentProvider;
|
||||
import com.android.email.provider.EmailContent;
|
||||
import com.android.email.provider.EmailContent.Attachment;
|
||||
import com.android.email.provider.EmailContent.AttachmentColumns;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
|
||||
public class LegacyConversions {
|
||||
|
||||
/**
|
||||
* Copy field-by-field from a "store" message to a "provider" message
|
||||
* @param message The message we've just downloaded
|
||||
* @param localMessage The message we'd like to write into the DB
|
||||
* @result true if dirty (changes were made)
|
||||
*/
|
||||
public static boolean updateMessageFields(EmailContent.Message localMessage, Message message,
|
||||
long accountId, long mailboxId) throws MessagingException {
|
||||
|
||||
Address[] from = message.getFrom();
|
||||
Address[] to = message.getRecipients(Message.RecipientType.TO);
|
||||
Address[] cc = message.getRecipients(Message.RecipientType.CC);
|
||||
Address[] bcc = message.getRecipients(Message.RecipientType.BCC);
|
||||
Address[] replyTo = message.getReplyTo();
|
||||
String subject = message.getSubject();
|
||||
Date sentDate = message.getSentDate();
|
||||
|
||||
if (from != null && from.length > 0) {
|
||||
localMessage.mDisplayName = from[0].toFriendly();
|
||||
}
|
||||
if (sentDate != null) {
|
||||
localMessage.mTimeStamp = sentDate.getTime();
|
||||
}
|
||||
if (subject != null) {
|
||||
localMessage.mSubject = subject;
|
||||
}
|
||||
// public String mPreview;
|
||||
// public boolean mFlagRead = false;
|
||||
|
||||
// Keep the message in the "unloaded" state until it has (at least) a display name.
|
||||
// This prevents early flickering of empty messages in POP download.
|
||||
if (localMessage.mFlagLoaded != EmailContent.Message.LOADED) {
|
||||
if (localMessage.mDisplayName == null || "".equals(localMessage.mDisplayName)) {
|
||||
localMessage.mFlagLoaded = EmailContent.Message.NOT_LOADED;
|
||||
} else {
|
||||
localMessage.mFlagLoaded = EmailContent.Message.PARTIALLY_LOADED;
|
||||
}
|
||||
}
|
||||
// TODO handle flags, favorites, and read/unread
|
||||
// public boolean mFlagFavorite = false;
|
||||
// public boolean mFlagAttachment = false;
|
||||
// public int mFlags = 0;
|
||||
//
|
||||
// public String mTextInfo;
|
||||
// public String mHtmlInfo;
|
||||
//
|
||||
localMessage.mServerId = message.getUid();
|
||||
// public int mServerIntId;
|
||||
// public String mClientId;
|
||||
// public String mMessageId;
|
||||
// public String mThreadId;
|
||||
//
|
||||
// public long mBodyKey;
|
||||
localMessage.mMailboxKey = mailboxId;
|
||||
localMessage.mAccountKey = accountId;
|
||||
// public long mReferenceKey;
|
||||
//
|
||||
// public String mSender;
|
||||
if (from != null && from.length > 0) {
|
||||
localMessage.mFrom = Address.pack(from);
|
||||
}
|
||||
|
||||
localMessage.mTo = Address.pack(to);
|
||||
localMessage.mCc = Address.pack(cc);
|
||||
localMessage.mBcc = Address.pack(bcc);
|
||||
localMessage.mReplyTo = Address.pack(replyTo);
|
||||
|
||||
// public String mServerVersion;
|
||||
//
|
||||
// public String mText;
|
||||
// public String mHtml;
|
||||
//
|
||||
// // Can be used while building messages, but is NOT saved by the Provider
|
||||
// transient public ArrayList<Attachment> mAttachments = null;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy body text (plain and/or HTML) from MimeMessage to provider Message
|
||||
*
|
||||
* TODO: Take a closer look at textInfo and deal with it if necessary.
|
||||
*/
|
||||
public static boolean updateBodyFields(EmailContent.Body body,
|
||||
EmailContent.Message localMessage, ArrayList<Part> viewables)
|
||||
throws MessagingException {
|
||||
|
||||
body.mMessageKey = localMessage.mId;
|
||||
|
||||
StringBuffer sbHtml = new StringBuffer();
|
||||
StringBuffer sbText = new StringBuffer();
|
||||
for (Part viewable : viewables) {
|
||||
String text = MimeUtility.getTextFromPart(viewable);
|
||||
if ("text/html".equalsIgnoreCase(viewable.getMimeType())) {
|
||||
if (sbHtml.length() > 0) {
|
||||
sbHtml.append('\n');
|
||||
}
|
||||
sbHtml.append(text);
|
||||
} else {
|
||||
if (sbText.length() > 0) {
|
||||
sbText.append('\n');
|
||||
}
|
||||
sbText.append(text);
|
||||
}
|
||||
}
|
||||
|
||||
// write the combined data to the body part
|
||||
if (sbText.length() != 0) {
|
||||
localMessage.mTextInfo = "X;X;8;" + sbText.length()*2;
|
||||
body.mTextContent = sbText.toString();
|
||||
}
|
||||
if (sbHtml.length() != 0) {
|
||||
localMessage.mHtmlInfo = "X;X;8;" + sbHtml.length()*2;
|
||||
body.mHtmlContent = sbHtml.toString();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy attachments from MimeMessage to provider Message.
|
||||
*
|
||||
* @param context a context for file operations
|
||||
* @param localMessage the attachments will be built against this message
|
||||
* @param message the original message from POP or IMAP, that may have attachments
|
||||
* @return true if it succeeded
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void updateAttachments(Context context, EmailContent.Message localMessage,
|
||||
ArrayList<Part> attachments) throws MessagingException, IOException {
|
||||
localMessage.mAttachments = null;
|
||||
for (Part attachmentPart : attachments) {
|
||||
addOneAttachment(context, localMessage, attachmentPart);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a single attachment part to the message
|
||||
*
|
||||
* TODO: This will simply add to any existing attachments - could this ever happen? If so,
|
||||
* change it to find existing attachments and delete/merge them.
|
||||
* TODO: Take a closer look at encoding and deal with it if necessary.
|
||||
*
|
||||
* @param context a context for file operations
|
||||
* @param localMessage the attachments will be built against this message
|
||||
* @param part a single attachment part from POP or IMAP
|
||||
* @return true if it succeeded
|
||||
* @throws IOException
|
||||
*/
|
||||
private static void addOneAttachment(Context context, EmailContent.Message localMessage,
|
||||
Part part) throws MessagingException, IOException {
|
||||
|
||||
Attachment localAttachment = new Attachment();
|
||||
|
||||
// Transfer fields from mime format to provider format
|
||||
String contentType = MimeUtility.unfoldAndDecode(part.getContentType());
|
||||
String name = MimeUtility.getHeaderParameter(contentType, "name");
|
||||
if (name == null) {
|
||||
String contentDisposition = MimeUtility.unfoldAndDecode(part.getContentType());
|
||||
name = MimeUtility.getHeaderParameter(contentDisposition, "filename");
|
||||
}
|
||||
|
||||
// Try to pull size from disposition (if not downloaded)
|
||||
long size = 0;
|
||||
String disposition = part.getDisposition();
|
||||
if (disposition != null) {
|
||||
String s = MimeUtility.getHeaderParameter(disposition, "size");
|
||||
if (s != null) {
|
||||
size = Long.parseLong(s);
|
||||
}
|
||||
}
|
||||
|
||||
// Get partId for unloaded IMAP attachments (if any)
|
||||
// This is only provided (and used) when we have structure but not the actual attachment
|
||||
String[] partIds = part.getHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA);
|
||||
String partId = partIds != null ? partIds[0] : null;;
|
||||
|
||||
localAttachment.mFileName = MimeUtility.getHeaderParameter(contentType, "name");
|
||||
localAttachment.mMimeType = part.getMimeType();
|
||||
localAttachment.mSize = size; // May be reset below if file handled
|
||||
localAttachment.mContentId = part.getContentId();
|
||||
localAttachment.mContentUri = null; // Will be set when file is saved
|
||||
localAttachment.mMessageKey = localMessage.mId;
|
||||
localAttachment.mLocation = partId;
|
||||
localAttachment.mEncoding = "B"; // TODO - convert other known encodings
|
||||
|
||||
// Save the attachment (so far) in order to obtain an id
|
||||
localAttachment.save(context);
|
||||
|
||||
// If an attachment body was actually provided, we need to write the file now
|
||||
// TODO this should be separated so it can be reused for attachment downloads
|
||||
if (part.getBody() != null) {
|
||||
long attachmentId = localAttachment.mId;
|
||||
long accountId = localMessage.mAccountKey;
|
||||
|
||||
InputStream in = part.getBody().getInputStream();
|
||||
|
||||
File saveIn = AttachmentProvider.getAttachmentDirectory(context, accountId);
|
||||
if (!saveIn.exists()) {
|
||||
saveIn.mkdirs();
|
||||
}
|
||||
File saveAs = AttachmentProvider.getAttachmentFilename(context, accountId,
|
||||
attachmentId);
|
||||
saveAs.createNewFile();
|
||||
FileOutputStream out = new FileOutputStream(saveAs);
|
||||
long copySize = IOUtils.copy(in, out);
|
||||
in.close();
|
||||
out.close();
|
||||
|
||||
// update the attachment with the extra information we now know
|
||||
String contentUriString = AttachmentProvider.getAttachmentUri(
|
||||
accountId, attachmentId).toString();
|
||||
|
||||
localAttachment.mSize = copySize;
|
||||
localAttachment.mContentUri = contentUriString;
|
||||
|
||||
// update the attachment in the database as well
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(AttachmentColumns.SIZE, copySize);
|
||||
cv.put(AttachmentColumns.CONTENT_URI, contentUriString);
|
||||
Uri uri = ContentUris.withAppendedId(Attachment.CONTENT_URI, attachmentId);
|
||||
context.getContentResolver().update(uri, cv, null, null);
|
||||
}
|
||||
|
||||
if (localMessage.mAttachments == null) {
|
||||
localMessage.mAttachments = new ArrayList<Attachment>();
|
||||
}
|
||||
localMessage.mAttachments.add(localAttachment);
|
||||
localMessage.mFlagAttachment = true;
|
||||
}
|
||||
}
|
@ -16,7 +16,6 @@
|
||||
|
||||
package com.android.email;
|
||||
|
||||
import com.android.email.mail.Address;
|
||||
import com.android.email.mail.FetchProfile;
|
||||
import com.android.email.mail.Flag;
|
||||
import com.android.email.mail.Folder;
|
||||
@ -29,7 +28,6 @@ import com.android.email.mail.Store;
|
||||
import com.android.email.mail.StoreSynchronizer;
|
||||
import com.android.email.mail.Folder.FolderType;
|
||||
import com.android.email.mail.Folder.OpenMode;
|
||||
import com.android.email.mail.internet.MimeMessage;
|
||||
import com.android.email.mail.internet.MimeUtility;
|
||||
import com.android.email.mail.store.LocalStore;
|
||||
import com.android.email.mail.store.LocalStore.LocalFolder;
|
||||
@ -46,9 +44,9 @@ import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Process;
|
||||
import android.util.Config;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
@ -781,7 +779,7 @@ public class MessagingController implements Runnable {
|
||||
if (localMessage != null) {
|
||||
try {
|
||||
// Copy the fields that are available into the message
|
||||
updateMessageFields(localMessage,
|
||||
LegacyConversions.updateMessageFields(localMessage,
|
||||
message, account.mId, folder.mId);
|
||||
// Commit the message to the local store
|
||||
saveOrUpdate(localMessage);
|
||||
@ -904,6 +902,7 @@ public class MessagingController implements Runnable {
|
||||
// this is going to be inefficient and duplicate work we've already done. 2. It's going
|
||||
// back to the DB for a local message that we already had (and discarded).
|
||||
|
||||
// For small messages, we specify "body", which returns everything (incl. attachments)
|
||||
fp = new FetchProfile();
|
||||
fp.add(FetchProfile.Item.BODY);
|
||||
remoteFolder.fetch(smallMessages.toArray(new Message[smallMessages.size()]), fp,
|
||||
@ -934,36 +933,63 @@ public class MessagingController implements Runnable {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
|
||||
if (localMessage != null) {
|
||||
EmailContent.Body body = EmailContent.Body.restoreBodyWithId(
|
||||
mContext, localMessage.mId);
|
||||
if (body == null) {
|
||||
body = new EmailContent.Body();
|
||||
}
|
||||
try {
|
||||
// Copy the fields that are available into the message
|
||||
updateMessageFields(localMessage,
|
||||
message, account.mId, folder.mId);
|
||||
updateBodyFields(body, localMessage, message);
|
||||
// TODO should updateMessageFields do this for us?
|
||||
// localMessage.mFlagLoaded = EmailContent.Message.LOADED;
|
||||
// Commit the message to the local store
|
||||
saveOrUpdate(localMessage);
|
||||
saveOrUpdate(body);
|
||||
} catch (MessagingException me) {
|
||||
Log.e(Email.LOG_TAG,
|
||||
"Error while copying downloaded message." + me);
|
||||
}
|
||||
|
||||
if (localMessage == null) {
|
||||
Log.d(Email.LOG_TAG, "Could not retrieve message from db, UUID="
|
||||
+ message.getUid());
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
|
||||
EmailContent.Body body = EmailContent.Body.restoreBodyWithId(
|
||||
mContext, localMessage.mId);
|
||||
if (body == null) {
|
||||
body = new EmailContent.Body();
|
||||
}
|
||||
try {
|
||||
// Copy the fields that are available into the message object
|
||||
LegacyConversions.updateMessageFields(localMessage, message,
|
||||
account.mId, folder.mId);
|
||||
|
||||
// Now process body parts & attachments
|
||||
ArrayList<Part> viewables = new ArrayList<Part>();
|
||||
ArrayList<Part> attachments = new ArrayList<Part>();
|
||||
MimeUtility.collectParts(message, viewables, attachments);
|
||||
|
||||
LegacyConversions.updateBodyFields(body, localMessage, viewables);
|
||||
|
||||
// Commit the message & body to the local store immediately
|
||||
saveOrUpdate(localMessage);
|
||||
saveOrUpdate(body);
|
||||
|
||||
// process (and save) attachments
|
||||
LegacyConversions.updateAttachments(mContext, localMessage,
|
||||
attachments);
|
||||
|
||||
// One last update of message with two updated flags
|
||||
localMessage.mFlagLoaded = EmailContent.Message.LOADED;
|
||||
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(EmailContent.MessageColumns.FLAG_ATTACHMENT,
|
||||
localMessage.mFlagAttachment);
|
||||
cv.put(EmailContent.MessageColumns.FLAG_LOADED,
|
||||
localMessage.mFlagLoaded);
|
||||
Uri uri = ContentUris.withAppendedId(
|
||||
EmailContent.Message.CONTENT_URI, localMessage.mId);
|
||||
mContext.getContentResolver().update(uri, cv, null, null);
|
||||
|
||||
} catch (MessagingException me) {
|
||||
Log.e(Email.LOG_TAG,
|
||||
"Error while copying downloaded message." + me);
|
||||
}
|
||||
|
||||
} catch (RuntimeException rte) {
|
||||
Log.e(Email.LOG_TAG,
|
||||
"Error while storing downloaded message." + e.toString());
|
||||
"Error while storing downloaded message." + rte.toString());
|
||||
} catch (IOException ioe) {
|
||||
Log.e(Email.LOG_TAG,
|
||||
"Error while storing attachment." + ioe.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void messageStarted(String uid, int number, int ofTotal) {
|
||||
}
|
||||
});
|
||||
@ -1113,127 +1139,6 @@ public class MessagingController implements Runnable {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy field-by-field from a "store" message to a "provider" message
|
||||
* @param message The message we've just downloaded
|
||||
* @param localMessage The message we'd like to write into the DB
|
||||
* @result true if dirty (changes were made)
|
||||
*/
|
||||
/* package */ boolean updateMessageFields(EmailContent.Message localMessage, Message message,
|
||||
long accountId, long mailboxId) throws MessagingException {
|
||||
|
||||
Address[] from = message.getFrom();
|
||||
Address[] to = message.getRecipients(Message.RecipientType.TO);
|
||||
Address[] cc = message.getRecipients(Message.RecipientType.CC);
|
||||
Address[] bcc = message.getRecipients(Message.RecipientType.BCC);
|
||||
Address[] replyTo = message.getReplyTo();
|
||||
String subject = message.getSubject();
|
||||
Date sentDate = message.getSentDate();
|
||||
|
||||
if (from != null && from.length > 0) {
|
||||
localMessage.mDisplayName = from[0].toFriendly();
|
||||
}
|
||||
if (sentDate != null) {
|
||||
localMessage.mTimeStamp = sentDate.getTime();
|
||||
}
|
||||
if (subject != null) {
|
||||
localMessage.mSubject = subject;
|
||||
}
|
||||
// public String mPreview;
|
||||
// public boolean mFlagRead = false;
|
||||
|
||||
// Keep the message in the "unloaded" state until it has (at least) a display name.
|
||||
// This prevents early flickering of empty messages in POP download.
|
||||
if (localMessage.mFlagLoaded != EmailContent.Message.LOADED) {
|
||||
if (localMessage.mDisplayName == null || "".equals(localMessage.mDisplayName)) {
|
||||
localMessage.mFlagLoaded = EmailContent.Message.NOT_LOADED;
|
||||
} else {
|
||||
localMessage.mFlagLoaded = EmailContent.Message.PARTIALLY_LOADED;
|
||||
}
|
||||
}
|
||||
// public boolean mFlagFavorite = false;
|
||||
// public boolean mFlagAttachment = false;
|
||||
// public int mFlags = 0;
|
||||
//
|
||||
// public String mTextInfo;
|
||||
// public String mHtmlInfo;
|
||||
//
|
||||
localMessage.mServerId = message.getUid();
|
||||
// public int mServerIntId;
|
||||
// public String mClientId;
|
||||
// public String mMessageId;
|
||||
// public String mThreadId;
|
||||
//
|
||||
// public long mBodyKey;
|
||||
localMessage.mMailboxKey = mailboxId;
|
||||
localMessage.mAccountKey = accountId;
|
||||
// public long mReferenceKey;
|
||||
//
|
||||
// public String mSender;
|
||||
if (from != null && from.length > 0) {
|
||||
localMessage.mFrom = Address.pack(from);
|
||||
}
|
||||
|
||||
if (to != null && to.length > 0) {
|
||||
localMessage.mTo = Address.pack(to);
|
||||
}
|
||||
if (cc != null && cc.length > 0) {
|
||||
localMessage.mCc = Address.pack(cc);
|
||||
}
|
||||
if (bcc != null && bcc.length > 0) {
|
||||
localMessage.mBcc = Address.pack(bcc);
|
||||
}
|
||||
if (replyTo != null && replyTo.length > 0) {
|
||||
localMessage.mReplyTo = Address.pack(replyTo);
|
||||
}
|
||||
//
|
||||
// public String mServerVersion;
|
||||
//
|
||||
// public String mText;
|
||||
// public String mHtml;
|
||||
//
|
||||
// // Can be used while building messages, but is NOT saved by the Provider
|
||||
// transient public ArrayList<Attachment> mAttachments = null;
|
||||
//
|
||||
// public static final int UNREAD = 0;
|
||||
// public static final int READ = 1;
|
||||
// public static final int DELETED = 2;
|
||||
//
|
||||
// public static final int NOT_LOADED = 0;
|
||||
// public static final int LOADED = 1;
|
||||
// public static final int PARTIALLY_LOADED = 2;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy body text (plain and/or HTML) from MimeMessage to provider Message
|
||||
*/
|
||||
/* package */ boolean updateBodyFields(EmailContent.Body body,
|
||||
EmailContent.Message localMessage, Message message) throws MessagingException {
|
||||
|
||||
body.mMessageKey = localMessage.mId;
|
||||
|
||||
Part htmlPart = MimeUtility.findFirstPartByMimeType(message, "text/html");
|
||||
Part textPart = MimeUtility.findFirstPartByMimeType(message, "text/plain");
|
||||
|
||||
if (textPart != null) {
|
||||
String text = MimeUtility.getTextFromPart(textPart);
|
||||
if (text != null) {
|
||||
localMessage.mTextInfo = "X;X;8;" + text.length()*2;
|
||||
body.mTextContent = text;
|
||||
}
|
||||
}
|
||||
if (htmlPart != null) {
|
||||
String html = MimeUtility.getTextFromPart(htmlPart);
|
||||
if (html != null) {
|
||||
localMessage.mHtmlInfo = "X;X;8;" + html.length()*2;
|
||||
body.mHtmlContent = html;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void queuePendingCommand(EmailContent.Account account, PendingCommand command) {
|
||||
try {
|
||||
LocalStore localStore = (LocalStore) Store.getInstance(
|
||||
|
@ -103,8 +103,18 @@ public class AttachmentProvider extends ContentProvider {
|
||||
* the filename that should be used.
|
||||
*/
|
||||
public static File getAttachmentFilename(Context context, long accountId, long attachmentId) {
|
||||
return new File(
|
||||
context.getDatabasePath(accountId + ".db_att"), Long.toString(attachmentId));
|
||||
return new File(getAttachmentDirectory(context, accountId), Long.toString(attachmentId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the directory for a given attachment. This should be used by any code that is
|
||||
* going to *write* attachments.
|
||||
*
|
||||
* This does not create or write the directory. It simply builds the pathname that should be
|
||||
* used.
|
||||
*/
|
||||
public static File getAttachmentDirectory(Context context, long accountId) {
|
||||
return context.getDatabasePath(accountId + ".db_att");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
173
tests/src/com/android/email/LegacyConversionsTests.java
Normal file
173
tests/src/com/android/email/LegacyConversionsTests.java
Normal file
@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.email;
|
||||
|
||||
import com.android.email.mail.BodyPart;
|
||||
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.MessageTestUtils.MessageBuilder;
|
||||
import com.android.email.mail.MessageTestUtils.MultipartBuilder;
|
||||
import com.android.email.mail.internet.MimeHeader;
|
||||
import com.android.email.mail.internet.MimeUtility;
|
||||
import com.android.email.provider.EmailContent;
|
||||
import com.android.email.provider.EmailProvider;
|
||||
import com.android.email.provider.ProviderTestUtils;
|
||||
import com.android.email.provider.EmailContent.Attachment;
|
||||
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.test.ProviderTestCase2;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Tests of the Legacy Conversions code (used by MessagingController).
|
||||
*
|
||||
* NOTE: It would probably make sense to rewrite this using a MockProvider, instead of the
|
||||
* ProviderTestCase (which is a real provider running on a temp database). This would be more of
|
||||
* a true "unit test".
|
||||
*
|
||||
* You can run this entire test case with:
|
||||
* runtest -c com.android.email.LegacyConversionsTests email
|
||||
*/
|
||||
public class LegacyConversionsTests extends ProviderTestCase2<EmailProvider> {
|
||||
|
||||
EmailProvider mProvider;
|
||||
Context mProviderContext;
|
||||
Context mContext;
|
||||
|
||||
public LegacyConversionsTests() {
|
||||
super(EmailProvider.class, EmailProvider.EMAIL_AUTHORITY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
mProviderContext = getMockContext();
|
||||
mContext = getContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tearDown() throws Exception {
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: basic Legacy -> Provider Message conversions
|
||||
* TODO: basic Legacy -> Provider Body conversions
|
||||
* TODO: rainy day tests of all kinds
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sunny day test of adding attachments from an IMAP message.
|
||||
*/
|
||||
public void testAddAttachments() throws MessagingException, IOException {
|
||||
// Prepare a local message to add the attachments to
|
||||
final long accountId = 1;
|
||||
final long mailboxId = 1;
|
||||
final EmailContent.Message localMessage = ProviderTestUtils.setupMessage(
|
||||
"local-message", accountId, mailboxId, false, true, mProviderContext);
|
||||
|
||||
// Prepare a legacy message with attachments
|
||||
Part attachment1Part = MessageTestUtils.bodyPart("image/gif", null);
|
||||
attachment1Part.setHeader(MimeHeader.HEADER_CONTENT_TYPE,
|
||||
"image/gif;\n name=\"attachment1\"");
|
||||
attachment1Part.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64");
|
||||
attachment1Part.setHeader(MimeHeader.HEADER_CONTENT_DISPOSITION,
|
||||
"attachment;\n filename=\"attachment1\";\n size=100");
|
||||
attachment1Part.setHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA, "101");
|
||||
|
||||
Part attachment2Part = MessageTestUtils.bodyPart("image/jpg", null);
|
||||
attachment2Part.setHeader(MimeHeader.HEADER_CONTENT_TYPE,
|
||||
"image/jpg;\n name=\"attachment2\"");
|
||||
attachment2Part.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64");
|
||||
attachment2Part.setHeader(MimeHeader.HEADER_CONTENT_DISPOSITION,
|
||||
"attachment;\n filename=\"attachment2\";\n size=200");
|
||||
attachment2Part.setHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA, "102");
|
||||
|
||||
final Message legacyMessage = new MessageBuilder()
|
||||
.setBody(new MultipartBuilder("multipart/mixed")
|
||||
.addBodyPart(MessageTestUtils.bodyPart("text/html", null))
|
||||
.addBodyPart(new MultipartBuilder("multipart/mixed")
|
||||
.addBodyPart((BodyPart)attachment1Part)
|
||||
.addBodyPart((BodyPart)attachment2Part)
|
||||
.buildBodyPart())
|
||||
.build())
|
||||
.build();
|
||||
|
||||
// Now, convert from legacy to provider and see what happens
|
||||
ArrayList<Part> viewables = new ArrayList<Part>();
|
||||
ArrayList<Part> attachments = new ArrayList<Part>();
|
||||
MimeUtility.collectParts(legacyMessage, viewables, attachments);
|
||||
LegacyConversions.updateAttachments(mProviderContext, localMessage, attachments);
|
||||
|
||||
// Read back all attachments for message and check field values
|
||||
Uri uri = ContentUris.withAppendedId(Attachment.MESSAGE_ID_URI, localMessage.mId);
|
||||
Cursor c = mProviderContext.getContentResolver().query(uri, Attachment.CONTENT_PROJECTION,
|
||||
null, null, null);
|
||||
try {
|
||||
assertEquals(2, c.getCount());
|
||||
while (c.moveToNext()) {
|
||||
Attachment attachment = Attachment.getContent(c, Attachment.class);
|
||||
if ("101".equals(attachment.mLocation)) {
|
||||
checkAttachment("attachment1Part", attachment1Part, attachment);
|
||||
} else if ("102".equals(attachment.mLocation)) {
|
||||
checkAttachment("attachment2Part", attachment2Part, attachment);
|
||||
} else {
|
||||
fail("Unexpected attachment with location " + attachment.mLocation);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare attachment that was converted from Part (expected) to Provider Attachment (actual)
|
||||
*
|
||||
* TODO content URI should only be set if we also saved a file
|
||||
* TODO other data encodings
|
||||
*/
|
||||
private void checkAttachment(String tag, Part expected, EmailContent.Attachment actual)
|
||||
throws MessagingException {
|
||||
String contentType = MimeUtility.unfoldAndDecode(expected.getContentType());
|
||||
String expectedName = MimeUtility.getHeaderParameter(contentType, "name");
|
||||
assertEquals(tag, expectedName, actual.mFileName);
|
||||
assertEquals(tag, expected.getMimeType(), actual.mMimeType);
|
||||
String disposition = expected.getDisposition();
|
||||
String sizeString = MimeUtility.getHeaderParameter(disposition, "size");
|
||||
long expectedSize = Long.parseLong(sizeString);
|
||||
assertEquals(tag, expectedSize, actual.mSize);
|
||||
assertEquals(tag, expected.getContentId(), actual.mContentId);
|
||||
assertNull(tag, actual.mContentUri);
|
||||
assertTrue(tag, 0 != actual.mMessageKey);
|
||||
String expectedPartId =
|
||||
expected.getHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA)[0];
|
||||
assertEquals(tag, expectedPartId, actual.mLocation);
|
||||
assertEquals(tag, "B", actual.mEncoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Sunny day test of adding attachments from a POP message.
|
||||
*/
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user