1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.email.provider; 18 19 import android.annotation.TargetApi; 20 import android.content.ContentUris; 21 import android.content.ContentValues; 22 import android.content.Context; 23 import android.database.Cursor; 24 import android.net.Uri; 25 import android.os.ParcelFileDescriptor; 26 27 import com.android.email.LegacyConversions; 28 import com.android.emailcommon.Logging; 29 import com.android.emailcommon.internet.MimeUtility; 30 import com.android.emailcommon.mail.Message; 31 import com.android.emailcommon.mail.MessagingException; 32 import com.android.emailcommon.mail.Part; 33 import com.android.emailcommon.provider.Account; 34 import com.android.emailcommon.provider.EmailContent; 35 import com.android.emailcommon.provider.EmailContent.Attachment; 36 import com.android.emailcommon.provider.EmailContent.MessageColumns; 37 import com.android.emailcommon.provider.EmailContent.SyncColumns; 38 import com.android.emailcommon.provider.Mailbox; 39 import com.android.emailcommon.utility.ConversionUtilities; 40 import com.android.mail.utils.LogUtils; 41 import com.android.mail.utils.Utils; 42 43 import java.io.IOException; 44 import java.util.ArrayList; 45 46 public class Utilities { 47 /** 48 * Copy one downloaded message (which may have partially-loaded sections) 49 * into a newly created EmailProvider Message, given the account and mailbox 50 * 51 * @param message the remote message we've just downloaded 52 * @param account the account it will be stored into 53 * @param folder the mailbox it will be stored into 54 * @param loadStatus when complete, the message will be marked with this status (e.g. 55 * EmailContent.Message.LOADED) 56 */ 57 public static void copyOneMessageToProvider(Context context, Message message, Account account, 58 Mailbox folder, int loadStatus) { 59 EmailContent.Message localMessage = null; 60 Cursor c = null; 61 try { 62 c = context.getContentResolver().query( 63 EmailContent.Message.CONTENT_URI, 64 EmailContent.Message.CONTENT_PROJECTION, 65 EmailContent.MessageColumns.ACCOUNT_KEY + "=?" + 66 " AND " + MessageColumns.MAILBOX_KEY + "=?" + 67 " AND " + SyncColumns.SERVER_ID + "=?", 68 new String[] { 69 String.valueOf(account.mId), 70 String.valueOf(folder.mId), 71 String.valueOf(message.getUid()) 72 }, 73 null); 74 if (c == null) { 75 return; 76 } else if (c.moveToNext()) { 77 localMessage = EmailContent.getContent(context, c, EmailContent.Message.class); 78 } else { 79 localMessage = new EmailContent.Message(); 80 } 81 localMessage.mMailboxKey = folder.mId; 82 localMessage.mAccountKey = account.mId; 83 copyOneMessageToProvider(context, message, localMessage, loadStatus); 84 } finally { 85 if (c != null) { 86 c.close(); 87 } 88 } 89 } 90 91 /** 92 * Copy one downloaded message (which may have partially-loaded sections) 93 * into an already-created EmailProvider Message 94 * 95 * @param message the remote message we've just downloaded 96 * @param localMessage the EmailProvider Message, already created 97 * @param loadStatus when complete, the message will be marked with this status (e.g. 98 * EmailContent.Message.LOADED) 99 * @param context the context to be used for EmailProvider 100 */ 101 public static void copyOneMessageToProvider(Context context, Message message, 102 EmailContent.Message localMessage, int loadStatus) { 103 try { 104 EmailContent.Body body = null; 105 if (localMessage.mId != EmailContent.Message.NO_MESSAGE) { 106 body = EmailContent.Body.restoreBodyWithMessageId(context, localMessage.mId); 107 } 108 if (body == null) { 109 body = new EmailContent.Body(); 110 } 111 try { 112 // Copy the fields that are available into the message object 113 LegacyConversions.updateMessageFields(localMessage, message, 114 localMessage.mAccountKey, localMessage.mMailboxKey); 115 116 // Now process body parts & attachments 117 ArrayList<Part> viewables = new ArrayList<Part>(); 118 ArrayList<Part> attachments = new ArrayList<Part>(); 119 MimeUtility.collectParts(message, viewables, attachments); 120 121 final ConversionUtilities.BodyFieldData data = 122 ConversionUtilities.parseBodyFields(viewables); 123 124 // set body and local message values 125 localMessage.setFlags(data.isQuotedReply, data.isQuotedForward); 126 localMessage.mSnippet = data.snippet; 127 body.mTextContent = data.textContent; 128 body.mHtmlContent = data.htmlContent; 129 130 // Commit the message & body to the local store immediately 131 saveOrUpdate(localMessage, context); 132 body.mMessageKey = localMessage.mId; 133 saveOrUpdate(body, context); 134 135 // process (and save) attachments 136 if (loadStatus != EmailContent.Message.FLAG_LOADED_PARTIAL 137 && loadStatus != EmailContent.Message.FLAG_LOADED_UNKNOWN) { 138 // TODO(pwestbro): What should happen with unknown status? 139 LegacyConversions.updateAttachments(context, localMessage, attachments); 140 LegacyConversions.updateInlineAttachments(context, localMessage, viewables); 141 } else { 142 EmailContent.Attachment att = new EmailContent.Attachment(); 143 // Since we haven't actually loaded the attachment, we're just putting 144 // a dummy placeholder here. When the user taps on it, we'll load the attachment 145 // for real. 146 // TODO: This is not a great way to model this. What we're saying is, we don't 147 // have the complete message, without paying any attention to what we do have. 148 // Did the main body exceed the maximum initial size? If so, we really might 149 // not have any attachments at all, and we just need a button somewhere that 150 // says "load the rest of the message". 151 // Or, what if we were able to load some, but not all of the attachments? 152 // Then we should ideally not be dropping the data we have on the floor. 153 // Also, what behavior we have here really should be based on what protocol 154 // we're dealing with. If it's POP, then we don't actually know how many 155 // attachments we have until we've loaded the complete message. 156 // If it's IMAP, we should know that, and we should load all attachment 157 // metadata we can get, regardless of whether or not we have the complete 158 // message body. 159 att.mFileName = ""; 160 att.mSize = message.getSize(); 161 att.mMimeType = "text/plain"; 162 att.mMessageKey = localMessage.mId; 163 att.mAccountKey = localMessage.mAccountKey; 164 att.mFlags = Attachment.FLAG_DUMMY_ATTACHMENT; 165 att.save(context); 166 localMessage.mFlagAttachment = true; 167 } 168 169 // One last update of message with two updated flags 170 localMessage.mFlagLoaded = loadStatus; 171 172 ContentValues cv = new ContentValues(); 173 cv.put(EmailContent.MessageColumns.FLAG_ATTACHMENT, localMessage.mFlagAttachment); 174 cv.put(EmailContent.MessageColumns.FLAG_LOADED, localMessage.mFlagLoaded); 175 Uri uri = ContentUris.withAppendedId(EmailContent.Message.CONTENT_URI, 176 localMessage.mId); 177 context.getContentResolver().update(uri, cv, null, null); 178 179 } catch (MessagingException me) { 180 LogUtils.e(Logging.LOG_TAG, "Error while copying downloaded message." + me); 181 } 182 183 } catch (RuntimeException rte) { 184 LogUtils.e(Logging.LOG_TAG, "Error while storing downloaded message." + rte.toString()); 185 } catch (IOException ioe) { 186 LogUtils.e(Logging.LOG_TAG, "Error while storing attachment." + ioe.toString()); 187 } 188 } 189 190 public static void saveOrUpdate(EmailContent content, Context context) { 191 if (content.isSaved()) { 192 content.update(context, content.toContentValues()); 193 } else { 194 content.save(context); 195 } 196 } 197 198 /** 199 * Converts a string representing a file mode, such as "rw", into a bitmask suitable for use 200 * with {@link android.os.ParcelFileDescriptor#open}. 201 * <p> 202 * @param mode The string representation of the file mode. 203 * @return A bitmask representing the given file mode. 204 * @throws IllegalArgumentException if the given string does not match a known file mode. 205 */ 206 @TargetApi(19) 207 public static int parseMode(String mode) { 208 if (Utils.isRunningKitkatOrLater()) { 209 return ParcelFileDescriptor.parseMode(mode); 210 } 211 final int modeBits; 212 if ("r".equals(mode)) { 213 modeBits = ParcelFileDescriptor.MODE_READ_ONLY; 214 } else if ("w".equals(mode) || "wt".equals(mode)) { 215 modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY 216 | ParcelFileDescriptor.MODE_CREATE 217 | ParcelFileDescriptor.MODE_TRUNCATE; 218 } else if ("wa".equals(mode)) { 219 modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY 220 | ParcelFileDescriptor.MODE_CREATE 221 | ParcelFileDescriptor.MODE_APPEND; 222 } else if ("rw".equals(mode)) { 223 modeBits = ParcelFileDescriptor.MODE_READ_WRITE 224 | ParcelFileDescriptor.MODE_CREATE; 225 } else if ("rwt".equals(mode)) { 226 modeBits = ParcelFileDescriptor.MODE_READ_WRITE 227 | ParcelFileDescriptor.MODE_CREATE 228 | ParcelFileDescriptor.MODE_TRUNCATE; 229 } else { 230 throw new IllegalArgumentException("Bad mode '" + mode + "'"); 231 } 232 return modeBits; 233 } 234 } 235