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 17package com.android.email.provider; 18 19import android.content.ContentUris; 20import android.content.ContentValues; 21import android.content.Context; 22import android.database.Cursor; 23import android.net.Uri; 24 25import com.android.email.LegacyConversions; 26import com.android.emailcommon.Logging; 27import com.android.emailcommon.internet.MimeUtility; 28import com.android.emailcommon.mail.Message; 29import com.android.emailcommon.mail.MessagingException; 30import com.android.emailcommon.mail.Part; 31import com.android.emailcommon.provider.Account; 32import com.android.emailcommon.provider.EmailContent; 33import com.android.emailcommon.provider.EmailContent.Attachment; 34import com.android.emailcommon.provider.EmailContent.MessageColumns; 35import com.android.emailcommon.provider.EmailContent.SyncColumns; 36import com.android.emailcommon.provider.Mailbox; 37import com.android.emailcommon.utility.ConversionUtilities; 38import com.android.mail.utils.LogUtils; 39 40import java.io.IOException; 41import java.util.ArrayList; 42 43public class Utilities { 44 /** 45 * Copy one downloaded message (which may have partially-loaded sections) 46 * into a newly created EmailProvider Message, given the account and mailbox 47 * 48 * @param message the remote message we've just downloaded 49 * @param account the account it will be stored into 50 * @param folder the mailbox it will be stored into 51 * @param loadStatus when complete, the message will be marked with this status (e.g. 52 * EmailContent.Message.LOADED) 53 */ 54 public static void copyOneMessageToProvider(Context context, Message message, Account account, 55 Mailbox folder, int loadStatus) { 56 EmailContent.Message localMessage = null; 57 Cursor c = null; 58 try { 59 c = context.getContentResolver().query( 60 EmailContent.Message.CONTENT_URI, 61 EmailContent.Message.CONTENT_PROJECTION, 62 EmailContent.MessageColumns.ACCOUNT_KEY + "=?" + 63 " AND " + MessageColumns.MAILBOX_KEY + "=?" + 64 " AND " + SyncColumns.SERVER_ID + "=?", 65 new String[] { 66 String.valueOf(account.mId), 67 String.valueOf(folder.mId), 68 String.valueOf(message.getUid()) 69 }, 70 null); 71 if (c == null) { 72 return; 73 } else if (c.moveToNext()) { 74 localMessage = EmailContent.getContent(c, EmailContent.Message.class); 75 } else { 76 localMessage = new EmailContent.Message(); 77 } 78 localMessage.mMailboxKey = folder.mId; 79 localMessage.mAccountKey = account.mId; 80 copyOneMessageToProvider(context, message, localMessage, loadStatus); 81 } finally { 82 if (c != null) { 83 c.close(); 84 } 85 } 86 } 87 88 /** 89 * Copy one downloaded message (which may have partially-loaded sections) 90 * into an already-created EmailProvider Message 91 * 92 * @param message the remote message we've just downloaded 93 * @param localMessage the EmailProvider Message, already created 94 * @param loadStatus when complete, the message will be marked with this status (e.g. 95 * EmailContent.Message.LOADED) 96 * @param context the context to be used for EmailProvider 97 */ 98 public static void copyOneMessageToProvider(Context context, Message message, 99 EmailContent.Message localMessage, int loadStatus) { 100 try { 101 EmailContent.Body body = null; 102 if (localMessage.mId != EmailContent.Message.NO_MESSAGE) { 103 body = EmailContent.Body.restoreBodyWithMessageId(context, localMessage.mId); 104 } 105 if (body == null) { 106 body = new EmailContent.Body(); 107 } 108 try { 109 // Copy the fields that are available into the message object 110 LegacyConversions.updateMessageFields(localMessage, message, 111 localMessage.mAccountKey, localMessage.mMailboxKey); 112 113 // Now process body parts & attachments 114 ArrayList<Part> viewables = new ArrayList<Part>(); 115 ArrayList<Part> attachments = new ArrayList<Part>(); 116 MimeUtility.collectParts(message, viewables, attachments); 117 118 final ConversionUtilities.BodyFieldData data = 119 ConversionUtilities.parseBodyFields(viewables); 120 121 // set body and local message values 122 localMessage.setFlags(data.isQuotedReply, data.isQuotedForward); 123 localMessage.mSnippet = data.snippet; 124 body.mTextContent = data.textContent; 125 body.mHtmlContent = data.htmlContent; 126 body.mHtmlReply = data.htmlReply; 127 body.mTextReply = data.textReply; 128 body.mIntroText = data.introText; 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 } else { 141 EmailContent.Attachment att = new EmailContent.Attachment(); 142 // Since we haven't actually loaded the attachment, we're just putting 143 // a dummy placeholder here. When the user taps on it, we'll load the attachment 144 // for real. 145 // TODO: This is not a great way to model this. What we're saying is, we don't 146 // have the complete message, without paying any attention to what we do have. 147 // Did the main body exceed the maximum initial size? If so, we really might 148 // not have any attachments at all, and we just need a button somewhere that 149 // says "load the rest of the message". 150 // Or, what if we were able to load some, but not all of the attachments? 151 // Then we should ideally not be dropping the data we have on the floor. 152 // Also, what behavior we have here really should be based on what protocol 153 // we're dealing with. If it's POP, then we don't actually know how many 154 // attachments we have until we've loaded the complete message. 155 // If it's IMAP, we should know that, and we should load all attachment 156 // metadata we can get, regardless of whether or not we have the complete 157 // message body. 158 att.mFileName = ""; 159 att.mSize = message.getSize(); 160 att.mMimeType = "text/plain"; 161 att.mMessageKey = localMessage.mId; 162 att.mAccountKey = localMessage.mAccountKey; 163 att.mFlags = Attachment.FLAG_DUMMY_ATTACHMENT; 164 att.save(context); 165 localMessage.mFlagAttachment = true; 166 } 167 168 // One last update of message with two updated flags 169 localMessage.mFlagLoaded = loadStatus; 170 171 ContentValues cv = new ContentValues(); 172 cv.put(EmailContent.MessageColumns.FLAG_ATTACHMENT, localMessage.mFlagAttachment); 173 cv.put(EmailContent.MessageColumns.FLAG_LOADED, localMessage.mFlagLoaded); 174 Uri uri = ContentUris.withAppendedId(EmailContent.Message.CONTENT_URI, 175 localMessage.mId); 176 context.getContentResolver().update(uri, cv, null, null); 177 178 } catch (MessagingException me) { 179 LogUtils.e(Logging.LOG_TAG, "Error while copying downloaded message." + me); 180 } 181 182 } catch (RuntimeException rte) { 183 LogUtils.e(Logging.LOG_TAG, "Error while storing downloaded message." + rte.toString()); 184 } catch (IOException ioe) { 185 LogUtils.e(Logging.LOG_TAG, "Error while storing attachment." + ioe.toString()); 186 } 187 } 188 189 public static void saveOrUpdate(EmailContent content, Context context) { 190 if (content.isSaved()) { 191 content.update(context, content.toContentValues()); 192 } else { 193 content.save(context); 194 } 195 } 196 197} 198