2009-08-11 22:02:57 +00:00
|
|
|
/*
|
|
|
|
* 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;
|
|
|
|
|
2011-06-24 21:40:03 +00:00
|
|
|
import android.content.ContentUris;
|
|
|
|
import android.content.ContentValues;
|
|
|
|
import android.content.Context;
|
|
|
|
import android.database.Cursor;
|
|
|
|
import android.net.Uri;
|
2014-02-21 19:14:48 +00:00
|
|
|
import android.text.TextUtils;
|
2011-06-24 21:40:03 +00:00
|
|
|
|
2011-02-11 23:05:17 +00:00
|
|
|
import com.android.emailcommon.Logging;
|
2011-02-10 02:47:43 +00:00
|
|
|
import com.android.emailcommon.internet.MimeBodyPart;
|
|
|
|
import com.android.emailcommon.internet.MimeHeader;
|
|
|
|
import com.android.emailcommon.internet.MimeMessage;
|
|
|
|
import com.android.emailcommon.internet.MimeMultipart;
|
|
|
|
import com.android.emailcommon.internet.MimeUtility;
|
|
|
|
import com.android.emailcommon.internet.TextBody;
|
|
|
|
import com.android.emailcommon.mail.Address;
|
2014-03-24 20:55:35 +00:00
|
|
|
import com.android.emailcommon.mail.Base64Body;
|
2011-02-10 02:47:43 +00:00
|
|
|
import com.android.emailcommon.mail.Flag;
|
|
|
|
import com.android.emailcommon.mail.Message;
|
2011-04-28 00:12:06 +00:00
|
|
|
import com.android.emailcommon.mail.Message.RecipientType;
|
2011-02-10 02:47:43 +00:00
|
|
|
import com.android.emailcommon.mail.MessagingException;
|
2014-03-24 20:55:35 +00:00
|
|
|
import com.android.emailcommon.mail.Multipart;
|
2011-02-10 02:47:43 +00:00
|
|
|
import com.android.emailcommon.mail.Part;
|
2011-02-10 18:26:56 +00:00
|
|
|
import com.android.emailcommon.provider.EmailContent;
|
|
|
|
import com.android.emailcommon.provider.EmailContent.Attachment;
|
|
|
|
import com.android.emailcommon.provider.EmailContent.AttachmentColumns;
|
2011-05-14 00:26:27 +00:00
|
|
|
import com.android.emailcommon.provider.Mailbox;
|
2011-02-09 01:50:30 +00:00
|
|
|
import com.android.emailcommon.utility.AttachmentUtilities;
|
2012-06-28 17:40:46 +00:00
|
|
|
import com.android.mail.providers.UIProvider;
|
2013-05-26 04:32:32 +00:00
|
|
|
import com.android.mail.utils.LogUtils;
|
2014-05-21 17:25:08 +00:00
|
|
|
import com.google.common.annotations.VisibleForTesting;
|
2009-08-11 22:02:57 +00:00
|
|
|
|
|
|
|
import org.apache.commons.io.IOUtils;
|
|
|
|
|
2014-03-24 20:55:35 +00:00
|
|
|
import java.io.ByteArrayInputStream;
|
2009-08-11 22:02:57 +00:00
|
|
|
import java.io.File;
|
2014-03-24 20:55:35 +00:00
|
|
|
import java.io.FileNotFoundException;
|
2009-08-11 22:02:57 +00:00
|
|
|
import java.io.FileOutputStream;
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.io.InputStream;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.Date;
|
2010-03-08 21:52:09 +00:00
|
|
|
import java.util.HashMap;
|
2009-08-11 22:02:57 +00:00
|
|
|
|
|
|
|
public class LegacyConversions {
|
|
|
|
|
2009-10-14 15:20:59 +00:00
|
|
|
/** DO NOT CHECK IN "TRUE" */
|
|
|
|
private static final boolean DEBUG_ATTACHMENTS = false;
|
|
|
|
|
2010-03-08 21:52:09 +00:00
|
|
|
/** Used for mapping folder names to type codes (e.g. inbox, drafts, trash) */
|
|
|
|
private static final HashMap<String, Integer>
|
|
|
|
sServerMailboxNames = new HashMap<String, Integer>();
|
|
|
|
|
2009-08-11 22:02:57 +00:00
|
|
|
/**
|
|
|
|
* Copy field-by-field from a "store" message to a "provider" message
|
2014-04-18 20:24:18 +00:00
|
|
|
*
|
|
|
|
* @param message The message we've just downloaded (must be a MimeMessage)
|
2009-08-11 22:02:57 +00:00
|
|
|
* @param localMessage The message we'd like to write into the DB
|
2014-01-25 00:43:36 +00:00
|
|
|
* @return true if dirty (changes were made)
|
2009-08-11 22:02:57 +00:00
|
|
|
*/
|
2014-04-18 20:24:18 +00:00
|
|
|
public static boolean updateMessageFields(final EmailContent.Message localMessage,
|
|
|
|
final Message message, final long accountId, final long mailboxId)
|
|
|
|
throws MessagingException {
|
|
|
|
|
|
|
|
final Address[] from = message.getFrom();
|
|
|
|
final Address[] to = message.getRecipients(Message.RecipientType.TO);
|
|
|
|
final Address[] cc = message.getRecipients(Message.RecipientType.CC);
|
|
|
|
final Address[] bcc = message.getRecipients(Message.RecipientType.BCC);
|
|
|
|
final Address[] replyTo = message.getReplyTo();
|
|
|
|
final String subject = message.getSubject();
|
|
|
|
final Date sentDate = message.getSentDate();
|
|
|
|
final Date internalDate = message.getInternalDate();
|
2009-08-11 22:02:57 +00:00
|
|
|
|
|
|
|
if (from != null && from.length > 0) {
|
|
|
|
localMessage.mDisplayName = from[0].toFriendly();
|
|
|
|
}
|
|
|
|
if (sentDate != null) {
|
|
|
|
localMessage.mTimeStamp = sentDate.getTime();
|
2013-11-11 20:56:59 +00:00
|
|
|
} else if (internalDate != null) {
|
|
|
|
LogUtils.w(Logging.LOG_TAG, "No sentDate, falling back to internalDate");
|
|
|
|
localMessage.mTimeStamp = internalDate.getTime();
|
2009-08-11 22:02:57 +00:00
|
|
|
}
|
|
|
|
if (subject != null) {
|
|
|
|
localMessage.mSubject = subject;
|
|
|
|
}
|
2009-08-26 05:45:11 +00:00
|
|
|
localMessage.mFlagRead = message.isSet(Flag.SEEN);
|
2011-06-24 21:40:03 +00:00
|
|
|
if (message.isSet(Flag.ANSWERED)) {
|
|
|
|
localMessage.mFlags |= EmailContent.Message.FLAG_REPLIED_TO;
|
|
|
|
}
|
2009-08-11 22:02:57 +00:00
|
|
|
|
|
|
|
// 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.
|
2009-09-10 18:52:36 +00:00
|
|
|
if (localMessage.mFlagLoaded != EmailContent.Message.FLAG_LOADED_COMPLETE) {
|
2009-08-11 22:02:57 +00:00
|
|
|
if (localMessage.mDisplayName == null || "".equals(localMessage.mDisplayName)) {
|
2009-09-10 18:52:36 +00:00
|
|
|
localMessage.mFlagLoaded = EmailContent.Message.FLAG_LOADED_UNLOADED;
|
2009-08-11 22:02:57 +00:00
|
|
|
} else {
|
2009-09-10 18:52:36 +00:00
|
|
|
localMessage.mFlagLoaded = EmailContent.Message.FLAG_LOADED_PARTIAL;
|
2009-08-11 22:02:57 +00:00
|
|
|
}
|
|
|
|
}
|
2009-08-26 05:45:11 +00:00
|
|
|
localMessage.mFlagFavorite = message.isSet(Flag.FLAGGED);
|
2009-08-11 22:02:57 +00:00
|
|
|
// public boolean mFlagAttachment = false;
|
|
|
|
// public int mFlags = 0;
|
2009-09-02 06:19:12 +00:00
|
|
|
|
2009-08-11 22:02:57 +00:00
|
|
|
localMessage.mServerId = message.getUid();
|
2009-09-23 01:31:10 +00:00
|
|
|
if (internalDate != null) {
|
|
|
|
localMessage.mServerTimeStamp = internalDate.getTime();
|
|
|
|
}
|
2009-08-11 22:02:57 +00:00
|
|
|
// public String mClientId;
|
2009-10-06 21:20:09 +00:00
|
|
|
|
Follow-up to MimeMessage efficiency improvements.
I missed a case where message-id should not be set locally, which is
the case where the Mime parser clears all headers *and* does not find
a message-id. The parsed MimeMessage should accurately reflect this.
In the old code, the local id was created at construction time and then
immediately discarded by the parser (calling headers.clear()).
In the new code, I was generating a message-id any time I couldn't find
one. Now, when explicitly cleared or removed, I set a boolean to inhibit
automatic generation of a new one.
I also missed the fact that a missing message-id no longer throws an
exception, it simply returns null, and so I changed the code that was
catching that exception to simply check for null.
(Note: Clearly, modeling of legacy behavior is becoming annoying here;
It would be better to do away with all of the automatic logic, and simply
generate message-id locally when appropriate: On locally-generated
messages. I don't want to touch this for the current release, but I left
a note in the code to this effect.)
Bug: 2504774
Change-Id: Ibfcbd2363c7ae39ee6d44e4c3295f88258cb4945
2010-03-11 00:42:49 +00:00
|
|
|
// Only replace the local message-id if a new one was found. This is seen in some ISP's
|
|
|
|
// which may deliver messages w/o a message-id header.
|
2014-04-18 20:24:18 +00:00
|
|
|
final String messageId = message.getMessageId();
|
Follow-up to MimeMessage efficiency improvements.
I missed a case where message-id should not be set locally, which is
the case where the Mime parser clears all headers *and* does not find
a message-id. The parsed MimeMessage should accurately reflect this.
In the old code, the local id was created at construction time and then
immediately discarded by the parser (calling headers.clear()).
In the new code, I was generating a message-id any time I couldn't find
one. Now, when explicitly cleared or removed, I set a boolean to inhibit
automatic generation of a new one.
I also missed the fact that a missing message-id no longer throws an
exception, it simply returns null, and so I changed the code that was
catching that exception to simply check for null.
(Note: Clearly, modeling of legacy behavior is becoming annoying here;
It would be better to do away with all of the automatic logic, and simply
generate message-id locally when appropriate: On locally-generated
messages. I don't want to touch this for the current release, but I left
a note in the code to this effect.)
Bug: 2504774
Change-Id: Ibfcbd2363c7ae39ee6d44e4c3295f88258cb4945
2010-03-11 00:42:49 +00:00
|
|
|
if (messageId != null) {
|
|
|
|
localMessage.mMessageId = messageId;
|
2009-10-06 21:20:09 +00:00
|
|
|
}
|
2009-09-02 06:19:12 +00:00
|
|
|
|
2009-08-11 22:02:57 +00:00
|
|
|
// public long mBodyKey;
|
|
|
|
localMessage.mMailboxKey = mailboxId;
|
|
|
|
localMessage.mAccountKey = accountId;
|
2009-09-02 06:19:12 +00:00
|
|
|
|
2009-08-11 22:02:57 +00:00
|
|
|
if (from != null && from.length > 0) {
|
2014-01-25 00:43:36 +00:00
|
|
|
localMessage.mFrom = Address.toString(from);
|
2009-08-11 22:02:57 +00:00
|
|
|
}
|
|
|
|
|
2014-01-25 00:43:36 +00:00
|
|
|
localMessage.mTo = Address.toString(to);
|
|
|
|
localMessage.mCc = Address.toString(cc);
|
|
|
|
localMessage.mBcc = Address.toString(bcc);
|
|
|
|
localMessage.mReplyTo = Address.toString(replyTo);
|
2009-08-11 22:02:57 +00:00
|
|
|
|
|
|
|
// public String mText;
|
|
|
|
// public String mHtml;
|
2009-09-02 06:19:12 +00:00
|
|
|
// public String mTextReply;
|
|
|
|
// public String mHtmlReply;
|
|
|
|
|
2009-08-11 22:02:57 +00:00
|
|
|
// // Can be used while building messages, but is NOT saved by the Provider
|
|
|
|
// transient public ArrayList<Attachment> mAttachments = null;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Copy attachments from MimeMessage to provider Message.
|
|
|
|
*
|
2014-04-18 20:24:18 +00:00
|
|
|
* @param context a context for file operations
|
2009-08-11 22:02:57 +00:00
|
|
|
* @param localMessage the attachments will be built against this message
|
2014-04-18 20:24:18 +00:00
|
|
|
* @param attachments the attachments to add
|
2009-08-11 22:02:57 +00:00
|
|
|
*/
|
2014-04-18 20:24:18 +00:00
|
|
|
public static void updateAttachments(final Context context,
|
|
|
|
final EmailContent.Message localMessage, final ArrayList<Part> attachments)
|
|
|
|
throws MessagingException, IOException {
|
2009-08-11 22:02:57 +00:00
|
|
|
localMessage.mAttachments = null;
|
|
|
|
for (Part attachmentPart : attachments) {
|
2011-02-06 08:54:39 +00:00
|
|
|
addOneAttachment(context, localMessage, attachmentPart);
|
2009-08-11 22:02:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-18 20:24:18 +00:00
|
|
|
public static void updateInlineAttachments(final Context context,
|
|
|
|
final EmailContent.Message localMessage, final ArrayList<Part> inlineAttachments)
|
|
|
|
throws MessagingException, IOException {
|
2014-02-21 19:14:48 +00:00
|
|
|
for (final Part inlinePart : inlineAttachments) {
|
|
|
|
final String disposition = MimeUtility.getHeaderParameter(
|
|
|
|
MimeUtility.unfoldAndDecode(inlinePart.getDisposition()), null);
|
|
|
|
if (!TextUtils.isEmpty(disposition)) {
|
|
|
|
// Treat inline parts as attachments
|
|
|
|
addOneAttachment(context, localMessage, inlinePart);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-08-11 22:02:57 +00:00
|
|
|
/**
|
2014-05-21 17:25:08 +00:00
|
|
|
* Convert a MIME Part object into an Attachment object. Separated for unit testing.
|
2009-10-14 15:20:59 +00:00
|
|
|
*
|
2014-05-21 17:25:08 +00:00
|
|
|
* @param part MIME part object to convert
|
|
|
|
* @return Populated Account object
|
|
|
|
* @throws MessagingException
|
2009-08-11 22:02:57 +00:00
|
|
|
*/
|
2014-05-21 17:25:08 +00:00
|
|
|
@VisibleForTesting
|
|
|
|
protected static Attachment mimePartToAttachment(final Part part) throws MessagingException {
|
2009-08-11 22:02:57 +00:00
|
|
|
// Transfer fields from mime format to provider format
|
2014-04-18 20:24:18 +00:00
|
|
|
final String contentType = MimeUtility.unfoldAndDecode(part.getContentType());
|
2014-05-21 17:25:08 +00:00
|
|
|
|
2009-08-11 22:02:57 +00:00
|
|
|
String name = MimeUtility.getHeaderParameter(contentType, "name");
|
2014-05-21 17:25:08 +00:00
|
|
|
if (TextUtils.isEmpty(name)) {
|
2014-04-18 20:24:18 +00:00
|
|
|
final String contentDisposition = MimeUtility.unfoldAndDecode(part.getDisposition());
|
2009-08-11 22:02:57 +00:00
|
|
|
name = MimeUtility.getHeaderParameter(contentDisposition, "filename");
|
|
|
|
}
|
|
|
|
|
2011-02-06 08:54:39 +00:00
|
|
|
// Incoming attachment: Try to pull size from disposition (if not downloaded yet)
|
2009-08-11 22:02:57 +00:00
|
|
|
long size = 0;
|
2014-04-18 20:24:18 +00:00
|
|
|
final String disposition = part.getDisposition();
|
2014-05-21 17:25:08 +00:00
|
|
|
if (!TextUtils.isEmpty(disposition)) {
|
2011-02-06 08:54:39 +00:00
|
|
|
String s = MimeUtility.getHeaderParameter(disposition, "size");
|
2014-05-21 17:25:08 +00:00
|
|
|
if (!TextUtils.isEmpty(s)) {
|
|
|
|
try {
|
|
|
|
size = Long.parseLong(s);
|
|
|
|
} catch (final NumberFormatException e) {
|
|
|
|
LogUtils.d(LogUtils.TAG, e, "Could not decode size \"%s\" from attachment part",
|
|
|
|
size);
|
|
|
|
}
|
2009-08-11 22:02:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get partId for unloaded IMAP attachments (if any)
|
|
|
|
// This is only provided (and used) when we have structure but not the actual attachment
|
2014-04-18 20:24:18 +00:00
|
|
|
final String[] partIds = part.getHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA);
|
|
|
|
final String partId = partIds != null ? partIds[0] : null;
|
2009-08-11 22:02:57 +00:00
|
|
|
|
2014-05-21 17:25:08 +00:00
|
|
|
final Attachment localAttachment = new Attachment();
|
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
// Run the mime type through inferMimeType in case we have something generic and can do
|
|
|
|
// better using the filename extension
|
2014-04-17 21:30:01 +00:00
|
|
|
localAttachment.mMimeType = AttachmentUtilities.inferMimeType(name, part.getMimeType());
|
2010-03-18 00:59:09 +00:00
|
|
|
localAttachment.mFileName = name;
|
2014-05-21 17:25:08 +00:00
|
|
|
localAttachment.mSize = size;
|
2009-08-11 22:02:57 +00:00
|
|
|
localAttachment.mContentId = part.getContentId();
|
2014-05-21 17:25:08 +00:00
|
|
|
localAttachment.setContentUri(null); // Will be rewritten by saveAttachmentBody
|
2009-08-11 22:02:57 +00:00
|
|
|
localAttachment.mLocation = partId;
|
2014-05-21 17:25:08 +00:00
|
|
|
localAttachment.mEncoding = "B"; // TODO - convert other known encodings
|
|
|
|
|
|
|
|
return localAttachment;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add a single attachment part to the message
|
|
|
|
*
|
|
|
|
* This will skip adding attachments if they are already found in the attachments table.
|
|
|
|
* The heuristic for this will fail (false-positive) if two identical attachments are
|
|
|
|
* included in a single POP3 message.
|
|
|
|
* TODO: Fix that, by (elsewhere) simulating an mLocation value based on the attachments
|
|
|
|
* position within the list of multipart/mixed elements. This would make every POP3 attachment
|
|
|
|
* unique, and might also simplify the code (since we could just look at the positions, and
|
|
|
|
* ignore the filename, etc.)
|
|
|
|
*
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
public static void addOneAttachment(final Context context,
|
|
|
|
final EmailContent.Message localMessage, final Part part)
|
|
|
|
throws MessagingException, IOException {
|
|
|
|
final Attachment localAttachment = mimePartToAttachment(part);
|
|
|
|
localAttachment.mMessageKey = localMessage.mId;
|
2010-12-09 01:11:04 +00:00
|
|
|
localAttachment.mAccountKey = localMessage.mAccountKey;
|
2009-08-11 22:02:57 +00:00
|
|
|
|
2009-10-14 15:20:59 +00:00
|
|
|
if (DEBUG_ATTACHMENTS) {
|
2013-05-26 04:32:32 +00:00
|
|
|
LogUtils.d(Logging.LOG_TAG, "Add attachment " + localAttachment);
|
2009-10-14 15:20:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// To prevent duplication - do we already have a matching attachment?
|
|
|
|
// The fields we'll check for equality are:
|
|
|
|
// mFileName, mMimeType, mContentId, mMessageKey, mLocation
|
|
|
|
// NOTE: This will false-positive if you attach the exact same file, twice, to a POP3
|
|
|
|
// message. We can live with that - you'll get one of the copies.
|
2014-04-18 20:24:18 +00:00
|
|
|
final Uri uri = ContentUris.withAppendedId(Attachment.MESSAGE_ID_URI, localMessage.mId);
|
|
|
|
final Cursor cursor = context.getContentResolver().query(uri, Attachment.CONTENT_PROJECTION,
|
2009-10-14 15:20:59 +00:00
|
|
|
null, null, null);
|
|
|
|
boolean attachmentFoundInDb = false;
|
|
|
|
try {
|
|
|
|
while (cursor.moveToNext()) {
|
2014-04-18 20:24:18 +00:00
|
|
|
final Attachment dbAttachment = new Attachment();
|
2011-02-02 21:23:06 +00:00
|
|
|
dbAttachment.restore(cursor);
|
2009-10-14 15:20:59 +00:00
|
|
|
// We test each of the fields here (instead of in SQL) because they may be
|
|
|
|
// null, or may be strings.
|
2014-04-18 20:24:18 +00:00
|
|
|
if (!TextUtils.equals(dbAttachment.mFileName, localAttachment.mFileName) ||
|
|
|
|
!TextUtils.equals(dbAttachment.mMimeType, localAttachment.mMimeType) ||
|
|
|
|
!TextUtils.equals(dbAttachment.mContentId, localAttachment.mContentId) ||
|
|
|
|
!TextUtils.equals(dbAttachment.mLocation, localAttachment.mLocation)) {
|
|
|
|
continue;
|
|
|
|
}
|
2009-10-14 15:20:59 +00:00
|
|
|
// We found a match, so use the existing attachment id, and stop looking/looping
|
|
|
|
attachmentFoundInDb = true;
|
|
|
|
localAttachment.mId = dbAttachment.mId;
|
|
|
|
if (DEBUG_ATTACHMENTS) {
|
2013-05-26 04:32:32 +00:00
|
|
|
LogUtils.d(Logging.LOG_TAG, "Skipped, found db attachment " + dbAttachment);
|
2009-10-14 15:20:59 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
cursor.close();
|
|
|
|
}
|
|
|
|
|
2009-08-11 22:02:57 +00:00
|
|
|
// Save the attachment (so far) in order to obtain an id
|
2009-10-14 15:20:59 +00:00
|
|
|
if (!attachmentFoundInDb) {
|
|
|
|
localAttachment.save(context);
|
|
|
|
}
|
2009-08-11 22:02:57 +00:00
|
|
|
|
|
|
|
// If an attachment body was actually provided, we need to write the file now
|
2011-02-06 08:54:39 +00:00
|
|
|
saveAttachmentBody(context, part, localAttachment, localMessage.mAccountKey);
|
2009-08-19 20:15:13 +00:00
|
|
|
|
|
|
|
if (localMessage.mAttachments == null) {
|
|
|
|
localMessage.mAttachments = new ArrayList<Attachment>();
|
|
|
|
}
|
|
|
|
localMessage.mAttachments.add(localAttachment);
|
|
|
|
localMessage.mFlagAttachment = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Save the body part of a single attachment, to a file in the attachments directory.
|
|
|
|
*/
|
2014-04-18 20:24:18 +00:00
|
|
|
public static void saveAttachmentBody(final Context context, final Part part,
|
|
|
|
final Attachment localAttachment, long accountId)
|
|
|
|
throws MessagingException, IOException {
|
2009-08-11 22:02:57 +00:00
|
|
|
if (part.getBody() != null) {
|
2014-04-17 23:19:23 +00:00
|
|
|
final long attachmentId = localAttachment.mId;
|
2009-08-11 22:02:57 +00:00
|
|
|
|
2014-04-17 23:19:23 +00:00
|
|
|
final File saveIn = AttachmentUtilities.getAttachmentDirectory(context, accountId);
|
2009-08-11 22:02:57 +00:00
|
|
|
|
2014-04-17 23:19:23 +00:00
|
|
|
if (!saveIn.isDirectory() && !saveIn.mkdirs()) {
|
|
|
|
throw new IOException("Could not create attachment directory");
|
2009-08-11 22:02:57 +00:00
|
|
|
}
|
2014-04-17 23:19:23 +00:00
|
|
|
final File saveAs = AttachmentUtilities.getAttachmentFilename(context, accountId,
|
2009-08-11 22:02:57 +00:00
|
|
|
attachmentId);
|
2014-04-17 23:19:23 +00:00
|
|
|
|
|
|
|
InputStream in = null;
|
|
|
|
FileOutputStream out = null;
|
|
|
|
final long copySize;
|
|
|
|
try {
|
|
|
|
in = part.getBody().getInputStream();
|
|
|
|
out = new FileOutputStream(saveAs);
|
|
|
|
copySize = IOUtils.copyLarge(in, out);
|
|
|
|
} finally {
|
|
|
|
if (in != null) {
|
|
|
|
in.close();
|
|
|
|
}
|
|
|
|
if (out != null) {
|
|
|
|
out.close();
|
|
|
|
}
|
|
|
|
}
|
2009-08-11 22:02:57 +00:00
|
|
|
|
|
|
|
// update the attachment with the extra information we now know
|
2014-04-17 23:19:23 +00:00
|
|
|
final String contentUriString = AttachmentUtilities.getAttachmentUri(
|
2009-08-11 22:02:57 +00:00
|
|
|
accountId, attachmentId).toString();
|
|
|
|
|
|
|
|
localAttachment.mSize = copySize;
|
2012-09-08 17:36:32 +00:00
|
|
|
localAttachment.setContentUri(contentUriString);
|
2009-08-11 22:02:57 +00:00
|
|
|
|
|
|
|
// update the attachment in the database as well
|
2014-04-17 23:19:23 +00:00
|
|
|
final ContentValues cv = new ContentValues(3);
|
2009-08-11 22:02:57 +00:00
|
|
|
cv.put(AttachmentColumns.SIZE, copySize);
|
|
|
|
cv.put(AttachmentColumns.CONTENT_URI, contentUriString);
|
2012-06-28 17:40:46 +00:00
|
|
|
cv.put(AttachmentColumns.UI_STATE, UIProvider.AttachmentState.SAVED);
|
2014-04-17 23:19:23 +00:00
|
|
|
final Uri uri = ContentUris.withAppendedId(Attachment.CONTENT_URI, attachmentId);
|
2009-08-11 22:02:57 +00:00
|
|
|
context.getContentResolver().update(uri, cv, null, null);
|
|
|
|
}
|
|
|
|
}
|
2009-08-19 20:15:13 +00:00
|
|
|
|
2009-09-25 21:54:32 +00:00
|
|
|
/**
|
|
|
|
* Read a complete Provider message into a legacy message (for IMAP upload). This
|
|
|
|
* is basically the equivalent of LocalFolder.getMessages() + LocalFolder.fetch().
|
|
|
|
*/
|
2014-04-18 20:24:18 +00:00
|
|
|
public static Message makeMessage(final Context context,
|
|
|
|
final EmailContent.Message localMessage)
|
2009-09-25 21:54:32 +00:00
|
|
|
throws MessagingException {
|
2014-04-18 20:24:18 +00:00
|
|
|
final MimeMessage message = new MimeMessage();
|
2009-09-25 21:54:32 +00:00
|
|
|
|
|
|
|
// LocalFolder.getMessages() equivalent: Copy message fields
|
|
|
|
message.setSubject(localMessage.mSubject == null ? "" : localMessage.mSubject);
|
2014-04-18 20:24:18 +00:00
|
|
|
final Address[] from = Address.fromHeader(localMessage.mFrom);
|
2009-09-25 21:54:32 +00:00
|
|
|
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);
|
2014-01-22 23:34:41 +00:00
|
|
|
message.setRecipients(RecipientType.TO, Address.fromHeader(localMessage.mTo));
|
|
|
|
message.setRecipients(RecipientType.CC, Address.fromHeader(localMessage.mCc));
|
|
|
|
message.setRecipients(RecipientType.BCC, Address.fromHeader(localMessage.mBcc));
|
|
|
|
message.setReplyTo(Address.fromHeader(localMessage.mReplyTo));
|
2009-09-25 21:54:32 +00:00
|
|
|
message.setInternalDate(new Date(localMessage.mServerTimeStamp));
|
|
|
|
message.setMessageId(localMessage.mMessageId);
|
|
|
|
|
|
|
|
// LocalFolder.fetch() equivalent: build body parts
|
|
|
|
message.setHeader(MimeHeader.HEADER_CONTENT_TYPE, "multipart/mixed");
|
2014-04-18 20:24:18 +00:00
|
|
|
final MimeMultipart mp = new MimeMultipart();
|
2009-09-25 21:54:32 +00:00
|
|
|
mp.setSubType("mixed");
|
|
|
|
message.setBody(mp);
|
|
|
|
|
|
|
|
try {
|
2014-04-18 20:24:18 +00:00
|
|
|
addTextBodyPart(mp, "text/html",
|
2009-09-25 21:54:32 +00:00
|
|
|
EmailContent.Body.restoreBodyHtmlWithMessageId(context, localMessage.mId));
|
|
|
|
} catch (RuntimeException rte) {
|
2013-05-26 04:32:32 +00:00
|
|
|
LogUtils.d(Logging.LOG_TAG, "Exception while reading html body " + rte.toString());
|
2009-09-25 21:54:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
2014-04-18 20:24:18 +00:00
|
|
|
addTextBodyPart(mp, "text/plain",
|
2009-09-25 21:54:32 +00:00
|
|
|
EmailContent.Body.restoreBodyTextWithMessageId(context, localMessage.mId));
|
|
|
|
} catch (RuntimeException rte) {
|
2013-05-26 04:32:32 +00:00
|
|
|
LogUtils.d(Logging.LOG_TAG, "Exception while reading text body " + rte.toString());
|
2009-09-25 21:54:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Attachments
|
2014-04-18 20:24:18 +00:00
|
|
|
final Uri uri = ContentUris.withAppendedId(Attachment.MESSAGE_ID_URI, localMessage.mId);
|
|
|
|
final Cursor attachments =
|
|
|
|
context.getContentResolver().query(uri, Attachment.CONTENT_PROJECTION,
|
|
|
|
null, null, null);
|
2014-03-24 20:55:35 +00:00
|
|
|
|
|
|
|
try {
|
|
|
|
while (attachments != null && attachments.moveToNext()) {
|
|
|
|
final Attachment att = new Attachment();
|
|
|
|
att.restore(attachments);
|
|
|
|
try {
|
|
|
|
final InputStream content;
|
|
|
|
if (att.mContentBytes != null) {
|
|
|
|
// This is generally only the case for synthetic attachments, such as those
|
|
|
|
// generated by unit tests or calendar invites
|
|
|
|
content = new ByteArrayInputStream(att.mContentBytes);
|
|
|
|
} else {
|
2014-04-07 23:26:04 +00:00
|
|
|
String contentUriString = att.getCachedFileUri();
|
|
|
|
if (TextUtils.isEmpty(contentUriString)) {
|
|
|
|
contentUriString = att.getContentUri();
|
|
|
|
}
|
|
|
|
if (TextUtils.isEmpty(contentUriString)) {
|
|
|
|
content = null;
|
|
|
|
} else {
|
|
|
|
final Uri contentUri = Uri.parse(contentUriString);
|
|
|
|
content = context.getContentResolver().openInputStream(contentUri);
|
|
|
|
}
|
2014-03-24 20:55:35 +00:00
|
|
|
}
|
|
|
|
final String mimeType = att.mMimeType;
|
|
|
|
final Long contentSize = att.mSize;
|
|
|
|
final String contentId = att.mContentId;
|
|
|
|
final String filename = att.mFileName;
|
2014-04-07 23:26:04 +00:00
|
|
|
if (content != null) {
|
|
|
|
addAttachmentPart(mp, mimeType, contentSize, filename, contentId, content);
|
|
|
|
} else {
|
|
|
|
LogUtils.e(LogUtils.TAG, "Could not open attachment file for upsync");
|
|
|
|
}
|
2014-03-24 20:55:35 +00:00
|
|
|
} catch (final FileNotFoundException e) {
|
|
|
|
LogUtils.e(LogUtils.TAG, "File Not Found error on %s while upsyncing message",
|
|
|
|
att.getCachedFileUri());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
if (attachments != null) {
|
|
|
|
attachments.close();
|
|
|
|
}
|
|
|
|
}
|
2009-09-25 21:54:32 +00:00
|
|
|
|
|
|
|
return message;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper method to add a body part for a given type of text, if found
|
|
|
|
*
|
2014-04-18 20:24:18 +00:00
|
|
|
* @param mp The text body part will be added to this multipart
|
2009-09-25 21:54:32 +00:00
|
|
|
* @param contentType The content-type of the text being added
|
2014-04-18 20:24:18 +00:00
|
|
|
* @param partText The text to add. If null, nothing happens
|
2009-09-25 21:54:32 +00:00
|
|
|
*/
|
2014-04-18 20:24:18 +00:00
|
|
|
private static void addTextBodyPart(final MimeMultipart mp, final String contentType,
|
|
|
|
final String partText)
|
|
|
|
throws MessagingException {
|
2009-09-25 21:54:32 +00:00
|
|
|
if (partText == null) {
|
|
|
|
return;
|
|
|
|
}
|
2014-04-18 20:24:18 +00:00
|
|
|
final TextBody body = new TextBody(partText);
|
|
|
|
final MimeBodyPart bp = new MimeBodyPart(body, contentType);
|
2009-09-25 21:54:32 +00:00
|
|
|
mp.addBodyPart(bp);
|
|
|
|
}
|
2010-01-20 09:36:01 +00:00
|
|
|
|
2014-03-24 20:55:35 +00:00
|
|
|
/**
|
|
|
|
* Helper method to add an attachment part
|
|
|
|
*
|
2014-04-18 20:24:18 +00:00
|
|
|
* @param mp Multipart message to append attachment part to
|
2014-03-24 20:55:35 +00:00
|
|
|
* @param contentType Mime type
|
|
|
|
* @param contentSize Attachment metadata: unencoded file size
|
2014-04-18 20:24:18 +00:00
|
|
|
* @param filename Attachment metadata: file name
|
|
|
|
* @param contentId as referenced from cid: uris in the message body (if applicable)
|
|
|
|
* @param content unencoded bytes
|
2014-03-24 20:55:35 +00:00
|
|
|
*/
|
2014-05-21 17:25:08 +00:00
|
|
|
@VisibleForTesting
|
|
|
|
protected static void addAttachmentPart(final Multipart mp, final String contentType,
|
2014-03-24 20:55:35 +00:00
|
|
|
final Long contentSize, final String filename, final String contentId,
|
|
|
|
final InputStream content) throws MessagingException {
|
|
|
|
final Base64Body body = new Base64Body(content);
|
|
|
|
final MimeBodyPart bp = new MimeBodyPart(body, contentType);
|
|
|
|
bp.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64");
|
2014-05-19 22:05:07 +00:00
|
|
|
bp.setHeader(MimeHeader.HEADER_CONTENT_DISPOSITION, "attachment;\n "
|
|
|
|
+ (!TextUtils.isEmpty(filename) ? "filename=\"" + filename + "\";" : "")
|
2014-03-24 20:55:35 +00:00
|
|
|
+ "size=" + contentSize);
|
|
|
|
if (contentId != null) {
|
|
|
|
bp.setHeader(MimeHeader.HEADER_CONTENT_ID, contentId);
|
|
|
|
}
|
|
|
|
mp.addBodyPart(bp);
|
|
|
|
}
|
2010-03-08 21:52:09 +00:00
|
|
|
|
|
|
|
/**
|
2011-02-06 08:54:39 +00:00
|
|
|
* Infer mailbox type from mailbox name. Used by MessagingController (for live folder sync).
|
2014-04-18 20:24:18 +00:00
|
|
|
*
|
|
|
|
* Deprecation: this should be configured in the UI, in conjunction with RF6154 support
|
2010-03-08 21:52:09 +00:00
|
|
|
*/
|
2014-04-18 20:24:18 +00:00
|
|
|
@Deprecated
|
2010-03-08 21:52:09 +00:00
|
|
|
public static synchronized int inferMailboxTypeFromName(Context context, String mailboxName) {
|
|
|
|
if (sServerMailboxNames.size() == 0) {
|
|
|
|
// preload the hashmap, one time only
|
|
|
|
sServerMailboxNames.put(
|
2014-07-31 17:01:52 +00:00
|
|
|
context.getString(R.string.mailbox_name_server_inbox),
|
2010-03-08 21:52:09 +00:00
|
|
|
Mailbox.TYPE_INBOX);
|
|
|
|
sServerMailboxNames.put(
|
2014-07-31 17:01:52 +00:00
|
|
|
context.getString(R.string.mailbox_name_server_outbox),
|
2010-03-08 21:52:09 +00:00
|
|
|
Mailbox.TYPE_OUTBOX);
|
|
|
|
sServerMailboxNames.put(
|
2014-07-31 17:01:52 +00:00
|
|
|
context.getString(R.string.mailbox_name_server_drafts),
|
2010-03-08 21:52:09 +00:00
|
|
|
Mailbox.TYPE_DRAFTS);
|
|
|
|
sServerMailboxNames.put(
|
2014-07-31 17:01:52 +00:00
|
|
|
context.getString(R.string.mailbox_name_server_trash),
|
2010-03-08 21:52:09 +00:00
|
|
|
Mailbox.TYPE_TRASH);
|
|
|
|
sServerMailboxNames.put(
|
2014-07-31 17:01:52 +00:00
|
|
|
context.getString(R.string.mailbox_name_server_sent),
|
2010-03-08 21:52:09 +00:00
|
|
|
Mailbox.TYPE_SENT);
|
|
|
|
sServerMailboxNames.put(
|
2014-07-31 17:01:52 +00:00
|
|
|
context.getString(R.string.mailbox_name_server_junk),
|
2010-03-08 21:52:09 +00:00
|
|
|
Mailbox.TYPE_JUNK);
|
|
|
|
}
|
|
|
|
if (mailboxName == null || mailboxName.length() == 0) {
|
2011-05-14 00:26:27 +00:00
|
|
|
return Mailbox.TYPE_MAIL;
|
2010-03-08 21:52:09 +00:00
|
|
|
}
|
2014-07-21 17:24:32 +00:00
|
|
|
Integer type = sServerMailboxNames.get(mailboxName);
|
2010-03-08 21:52:09 +00:00
|
|
|
if (type != null) {
|
|
|
|
return type;
|
|
|
|
}
|
2011-05-14 00:26:27 +00:00
|
|
|
return Mailbox.TYPE_MAIL;
|
2010-03-08 21:52:09 +00:00
|
|
|
}
|
2009-08-11 22:02:57 +00:00
|
|
|
}
|