/* * Copyright (C) 2012 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.provider; import android.annotation.TargetApi; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.os.ParcelFileDescriptor; import com.android.email.LegacyConversions; import com.android.emailcommon.Logging; import com.android.emailcommon.internet.MimeUtility; import com.android.emailcommon.mail.Message; import com.android.emailcommon.mail.MessagingException; import com.android.emailcommon.mail.Part; import com.android.emailcommon.provider.Account; import com.android.emailcommon.provider.EmailContent; import com.android.emailcommon.provider.EmailContent.Attachment; import com.android.emailcommon.provider.EmailContent.MessageColumns; import com.android.emailcommon.provider.EmailContent.SyncColumns; import com.android.emailcommon.provider.Mailbox; import com.android.emailcommon.utility.ConversionUtilities; import com.android.mail.utils.LogUtils; import com.android.mail.utils.Utils; import java.io.InputStream; import java.io.IOException; import java.util.ArrayList; public class Utilities { /** * Copy one downloaded message (which may have partially-loaded sections) * into a newly created EmailProvider Message, given the account and mailbox * * @param message the remote message we've just downloaded * @param account the account it will be stored into * @param folder the mailbox it will be stored into * @param loadStatus when complete, the message will be marked with this status (e.g. * EmailContent.Message.LOADED) */ public static void copyOneMessageToProvider(Context context, Message message, Account account, Mailbox folder, int loadStatus) { EmailContent.Message localMessage = null; Cursor c = null; try { c = context.getContentResolver().query( EmailContent.Message.CONTENT_URI, EmailContent.Message.CONTENT_PROJECTION, EmailContent.MessageColumns.ACCOUNT_KEY + "=?" + " AND " + MessageColumns.MAILBOX_KEY + "=?" + " AND " + SyncColumns.SERVER_ID + "=?", new String[] { String.valueOf(account.mId), String.valueOf(folder.mId), String.valueOf(message.getUid()) }, null); if (c == null) { return; } else if (c.moveToNext()) { localMessage = EmailContent.getContent(context, c, EmailContent.Message.class); } else { localMessage = new EmailContent.Message(); } localMessage.mMailboxKey = folder.mId; localMessage.mAccountKey = account.mId; copyOneMessageToProvider(context, message, localMessage, loadStatus); } finally { if (c != null) { c.close(); } } } /** * Copy one downloaded message (which may have partially-loaded sections) * into an already-created EmailProvider Message * * @param message the remote message we've just downloaded * @param localMessage the EmailProvider Message, already created * @param loadStatus when complete, the message will be marked with this status (e.g. * EmailContent.Message.LOADED) * @param context the context to be used for EmailProvider */ public static void copyOneMessageToProvider(Context context, Message message, EmailContent.Message localMessage, int loadStatus) { try { EmailContent.Body body = null; if (localMessage.mId != EmailContent.Message.NO_MESSAGE) { body = EmailContent.Body.restoreBodyWithMessageId(context, localMessage.mId); } if (body == null) { body = new EmailContent.Body(); } try { // Copy the fields that are available into the message object LegacyConversions.updateMessageFields(localMessage, message, localMessage.mAccountKey, localMessage.mMailboxKey); // Now process body parts & attachments ArrayList viewables = new ArrayList(); ArrayList attachments = new ArrayList(); MimeUtility.collectParts(message, viewables, attachments); // Don't close the viewables attachment InputStream yet final ConversionUtilities.BodyFieldData data = ConversionUtilities.parseBodyFields(viewables, false); // set body and local message values localMessage.setFlags(data.isQuotedReply, data.isQuotedForward); localMessage.mSnippet = data.snippet; body.mTextContent = data.textContent; body.mHtmlContent = data.htmlContent; // Commit the message & body to the local store immediately saveOrUpdate(localMessage, context); body.mMessageKey = localMessage.mId; saveOrUpdate(body, context); // process (and save) attachments if (loadStatus != EmailContent.Message.FLAG_LOADED_PARTIAL && loadStatus != EmailContent.Message.FLAG_LOADED_UNKNOWN) { // TODO(pwestbro): What should happen with unknown status? LegacyConversions.updateAttachments(context, localMessage, attachments); LegacyConversions.updateInlineAttachments(context, localMessage, viewables); } else { EmailContent.Attachment att = new EmailContent.Attachment(); // Since we haven't actually loaded the attachment, we're just putting // a dummy placeholder here. When the user taps on it, we'll load the attachment // for real. // TODO: This is not a great way to model this. What we're saying is, we don't // have the complete message, without paying any attention to what we do have. // Did the main body exceed the maximum initial size? If so, we really might // not have any attachments at all, and we just need a button somewhere that // says "load the rest of the message". // Or, what if we were able to load some, but not all of the attachments? // Then we should ideally not be dropping the data we have on the floor. // Also, what behavior we have here really should be based on what protocol // we're dealing with. If it's POP, then we don't actually know how many // attachments we have until we've loaded the complete message. // If it's IMAP, we should know that, and we should load all attachment // metadata we can get, regardless of whether or not we have the complete // message body. att.mFileName = ""; att.mSize = message.getSize(); att.mMimeType = "text/plain"; att.mMessageKey = localMessage.mId; att.mAccountKey = localMessage.mAccountKey; att.mFlags = Attachment.FLAG_DUMMY_ATTACHMENT; att.save(context); localMessage.mFlagAttachment = true; } // Close any parts that may still be open for (final Part part : viewables) { if (part.getBody() == null) { continue; } try { InputStream is = part.getBody().getInputStream(); if (is != null) { is.close(); } } catch (IOException | MessagingException io) { // Ignore } } // One last update of message with two updated flags localMessage.mFlagLoaded = loadStatus; 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); context.getContentResolver().update(uri, cv, null, null); } catch (MessagingException me) { LogUtils.e(Logging.LOG_TAG, "Error while copying downloaded message." + me); } } catch (RuntimeException rte) { LogUtils.e(Logging.LOG_TAG, "Error while storing downloaded message." + rte.toString()); } catch (IOException ioe) { LogUtils.e(Logging.LOG_TAG, "Error while storing attachment." + ioe.toString()); } } public static void saveOrUpdate(EmailContent content, Context context) { if (content.isSaved()) { content.update(context, content.toContentValues()); } else { content.save(context); } } /** * Converts a string representing a file mode, such as "rw", into a bitmask suitable for use * with {@link android.os.ParcelFileDescriptor#open}. *

* @param mode The string representation of the file mode. * @return A bitmask representing the given file mode. * @throws IllegalArgumentException if the given string does not match a known file mode. */ @TargetApi(19) public static int parseMode(String mode) { if (Utils.isRunningKitkatOrLater()) { return ParcelFileDescriptor.parseMode(mode); } final int modeBits; if ("r".equals(mode)) { modeBits = ParcelFileDescriptor.MODE_READ_ONLY; } else if ("w".equals(mode) || "wt".equals(mode)) { modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_TRUNCATE; } else if ("wa".equals(mode)) { modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_APPEND; } else if ("rw".equals(mode)) { modeBits = ParcelFileDescriptor.MODE_READ_WRITE | ParcelFileDescriptor.MODE_CREATE; } else if ("rwt".equals(mode)) { modeBits = ParcelFileDescriptor.MODE_READ_WRITE | ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_TRUNCATE; } else { throw new IllegalArgumentException("Bad mode '" + mode + "'"); } return modeBits; } }