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:
Andrew Stadler 2009-08-11 15:02:57 -07:00
parent a5abab243b
commit 3f1ac4da94
5 changed files with 530 additions and 155 deletions

View File

@ -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);

View 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;
}
}

View File

@ -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(

View File

@ -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

View 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.
*/
}