• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 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;
18 
19 import com.android.email.mail.Address;
20 import com.android.email.mail.Body;
21 import com.android.email.mail.Flag;
22 import com.android.email.mail.Folder;
23 import com.android.email.mail.Message;
24 import com.android.email.mail.MessagingException;
25 import com.android.email.mail.Part;
26 import com.android.email.mail.Message.RecipientType;
27 import com.android.email.mail.internet.MimeBodyPart;
28 import com.android.email.mail.internet.MimeHeader;
29 import com.android.email.mail.internet.MimeMessage;
30 import com.android.email.mail.internet.MimeMultipart;
31 import com.android.email.mail.internet.MimeUtility;
32 import com.android.email.mail.internet.TextBody;
33 import com.android.email.mail.store.LocalStore;
34 import com.android.email.provider.AttachmentProvider;
35 import com.android.email.provider.EmailContent;
36 import com.android.email.provider.EmailContent.Attachment;
37 import com.android.email.provider.EmailContent.AttachmentColumns;
38 import com.android.email.provider.EmailContent.Mailbox;
39 
40 import org.apache.commons.io.IOUtils;
41 
42 import android.content.ContentUris;
43 import android.content.ContentValues;
44 import android.content.Context;
45 import android.database.Cursor;
46 import android.net.Uri;
47 import android.provider.OpenableColumns;
48 import android.util.Log;
49 
50 import java.io.File;
51 import java.io.FileOutputStream;
52 import java.io.IOException;
53 import java.io.InputStream;
54 import java.util.ArrayList;
55 import java.util.Date;
56 import java.util.HashMap;
57 
58 public class LegacyConversions {
59 
60     /** DO NOT CHECK IN "TRUE" */
61     private static final boolean DEBUG_ATTACHMENTS = false;
62 
63     /** Used for mapping folder names to type codes (e.g. inbox, drafts, trash) */
64     private static final HashMap<String, Integer>
65             sServerMailboxNames = new HashMap<String, Integer>();
66 
67     /**
68      * Values for HEADER_ANDROID_BODY_QUOTED_PART to tag body parts
69      */
70     /* package */ static final String BODY_QUOTED_PART_REPLY = "quoted-reply";
71     /* package */ static final String BODY_QUOTED_PART_FORWARD = "quoted-forward";
72     /* package */ static final String BODY_QUOTED_PART_INTRO = "quoted-intro";
73 
74     /**
75      * Standard columns for querying content providers
76      */
77     private static final String[] ATTACHMENT_META_COLUMNS_PROJECTION = {
78         OpenableColumns.DISPLAY_NAME,
79         OpenableColumns.SIZE
80     };
81     private static final int ATTACHMENT_META_COLUMNS_SIZE = 1;
82 
83     /**
84      * Copy field-by-field from a "store" message to a "provider" message
85      * @param message The message we've just downloaded (must be a MimeMessage)
86      * @param localMessage The message we'd like to write into the DB
87      * @result true if dirty (changes were made)
88      */
updateMessageFields(EmailContent.Message localMessage, Message message, long accountId, long mailboxId)89     public static boolean updateMessageFields(EmailContent.Message localMessage, Message message,
90                 long accountId, long mailboxId) throws MessagingException {
91 
92         Address[] from = message.getFrom();
93         Address[] to = message.getRecipients(Message.RecipientType.TO);
94         Address[] cc = message.getRecipients(Message.RecipientType.CC);
95         Address[] bcc = message.getRecipients(Message.RecipientType.BCC);
96         Address[] replyTo = message.getReplyTo();
97         String subject = message.getSubject();
98         Date sentDate = message.getSentDate();
99         Date internalDate = message.getInternalDate();
100 
101         if (from != null && from.length > 0) {
102             localMessage.mDisplayName = from[0].toFriendly();
103         }
104         if (sentDate != null) {
105             localMessage.mTimeStamp = sentDate.getTime();
106         }
107         if (subject != null) {
108             localMessage.mSubject = subject;
109         }
110         localMessage.mFlagRead = message.isSet(Flag.SEEN);
111 
112         // Keep the message in the "unloaded" state until it has (at least) a display name.
113         // This prevents early flickering of empty messages in POP download.
114         if (localMessage.mFlagLoaded != EmailContent.Message.FLAG_LOADED_COMPLETE) {
115             if (localMessage.mDisplayName == null || "".equals(localMessage.mDisplayName)) {
116                 localMessage.mFlagLoaded = EmailContent.Message.FLAG_LOADED_UNLOADED;
117             } else {
118                 localMessage.mFlagLoaded = EmailContent.Message.FLAG_LOADED_PARTIAL;
119             }
120         }
121         localMessage.mFlagFavorite = message.isSet(Flag.FLAGGED);
122 //        public boolean mFlagAttachment = false;
123 //        public int mFlags = 0;
124 
125         localMessage.mServerId = message.getUid();
126         if (internalDate != null) {
127             localMessage.mServerTimeStamp = internalDate.getTime();
128         }
129 //        public String mClientId;
130 
131         // Only replace the local message-id if a new one was found.  This is seen in some ISP's
132         // which may deliver messages w/o a message-id header.
133         String messageId = ((MimeMessage)message).getMessageId();
134         if (messageId != null) {
135             localMessage.mMessageId = messageId;
136         }
137 
138 //        public long mBodyKey;
139         localMessage.mMailboxKey = mailboxId;
140         localMessage.mAccountKey = accountId;
141 
142         if (from != null && from.length > 0) {
143             localMessage.mFrom = Address.pack(from);
144         }
145 
146         localMessage.mTo = Address.pack(to);
147         localMessage.mCc = Address.pack(cc);
148         localMessage.mBcc = Address.pack(bcc);
149         localMessage.mReplyTo = Address.pack(replyTo);
150 
151 //        public String mText;
152 //        public String mHtml;
153 //        public String mTextReply;
154 //        public String mHtmlReply;
155 
156 //        // Can be used while building messages, but is NOT saved by the Provider
157 //        transient public ArrayList<Attachment> mAttachments = null;
158 
159         return true;
160     }
161 
162     /**
163      * Copy body text (plain and/or HTML) from MimeMessage to provider Message
164      */
updateBodyFields(EmailContent.Body body, EmailContent.Message localMessage, ArrayList<Part> viewables)165     public static boolean updateBodyFields(EmailContent.Body body,
166             EmailContent.Message localMessage, ArrayList<Part> viewables)
167             throws MessagingException {
168 
169         body.mMessageKey = localMessage.mId;
170 
171         StringBuffer sbHtml = null;
172         StringBuffer sbText = null;
173         StringBuffer sbHtmlReply = null;
174         StringBuffer sbTextReply = null;
175         StringBuffer sbIntroText = null;
176 
177         for (Part viewable : viewables) {
178             String text = MimeUtility.getTextFromPart(viewable);
179             String[] replyTags = viewable.getHeader(MimeHeader.HEADER_ANDROID_BODY_QUOTED_PART);
180             String replyTag = null;
181             if (replyTags != null && replyTags.length > 0) {
182                 replyTag = replyTags[0];
183             }
184             // Deploy text as marked by the various tags
185             boolean isHtml = "text/html".equalsIgnoreCase(viewable.getMimeType());
186 
187             if (replyTag != null) {
188                 boolean isQuotedReply = BODY_QUOTED_PART_REPLY.equalsIgnoreCase(replyTag);
189                 boolean isQuotedForward = BODY_QUOTED_PART_FORWARD.equalsIgnoreCase(replyTag);
190                 boolean isQuotedIntro = BODY_QUOTED_PART_INTRO.equalsIgnoreCase(replyTag);
191 
192                 if (isQuotedReply || isQuotedForward) {
193                     if (isHtml) {
194                         sbHtmlReply = appendTextPart(sbHtmlReply, text);
195                     } else {
196                         sbTextReply = appendTextPart(sbTextReply, text);
197                     }
198                     // Set message flags as well
199                     localMessage.mFlags &= ~EmailContent.Message.FLAG_TYPE_MASK;
200                     localMessage.mFlags |= isQuotedReply
201                             ? EmailContent.Message.FLAG_TYPE_REPLY
202                             : EmailContent.Message.FLAG_TYPE_FORWARD;
203                     continue;
204                 }
205                 if (isQuotedIntro) {
206                     sbIntroText = appendTextPart(sbIntroText, text);
207                     continue;
208                 }
209             }
210 
211             // Most of the time, just process regular body parts
212             if (isHtml) {
213                 sbHtml = appendTextPart(sbHtml, text);
214             } else {
215                 sbText = appendTextPart(sbText, text);
216             }
217         }
218 
219         // write the combined data to the body part
220         if (sbText != null && sbText.length() != 0) {
221             body.mTextContent = sbText.toString();
222         }
223         if (sbHtml != null && sbHtml.length() != 0) {
224             body.mHtmlContent = sbHtml.toString();
225         }
226         if (sbHtmlReply != null && sbHtmlReply.length() != 0) {
227             body.mHtmlReply = sbHtmlReply.toString();
228         }
229         if (sbTextReply != null && sbTextReply.length() != 0) {
230             body.mTextReply = sbTextReply.toString();
231         }
232         if (sbIntroText != null && sbIntroText.length() != 0) {
233             body.mIntroText = sbIntroText.toString();
234         }
235         return true;
236     }
237 
238     /**
239      * Helper function to append text to a StringBuffer, creating it if necessary.
240      * Optimization:  The majority of the time we are *not* appending - we should have a path
241      * that deals with single strings.
242      */
appendTextPart(StringBuffer sb, String newText)243     private static StringBuffer appendTextPart(StringBuffer sb, String newText) {
244         if (newText == null) {
245             return sb;
246         }
247         else if (sb == null) {
248             sb = new StringBuffer(newText);
249         } else {
250             if (sb.length() > 0) {
251                 sb.append('\n');
252             }
253             sb.append(newText);
254         }
255         return sb;
256     }
257 
258     /**
259      * Copy attachments from MimeMessage to provider Message.
260      *
261      * @param context a context for file operations
262      * @param localMessage the attachments will be built against this message
263      * @param attachments the attachments to add
264      * @param upgrading if true, we are upgrading a local account - handle attachments differently
265      * @throws IOException
266      */
updateAttachments(Context context, EmailContent.Message localMessage, ArrayList<Part> attachments, boolean upgrading)267     public static void updateAttachments(Context context, EmailContent.Message localMessage,
268             ArrayList<Part> attachments, boolean upgrading) throws MessagingException, IOException {
269         localMessage.mAttachments = null;
270         for (Part attachmentPart : attachments) {
271             addOneAttachment(context, localMessage, attachmentPart, upgrading);
272         }
273     }
274 
275     /**
276      * Add a single attachment part to the message
277      *
278      * This will skip adding attachments if they are already found in the attachments table.
279      * The heuristic for this will fail (false-positive) if two identical attachments are
280      * included in a single POP3 message.
281      * TODO: Fix that, by (elsewhere) simulating an mLocation value based on the attachments
282      * position within the list of multipart/mixed elements.  This would make every POP3 attachment
283      * unique, and might also simplify the code (since we could just look at the positions, and
284      * ignore the filename, etc.)
285      *
286      * TODO: Take a closer look at encoding and deal with it if necessary.
287      *
288      * @param context a context for file operations
289      * @param localMessage the attachments will be built against this message
290      * @param part a single attachment part from POP or IMAP
291      * @param upgrading true if upgrading a local account - handle attachments differently
292      * @throws IOException
293      */
addOneAttachment(Context context, EmailContent.Message localMessage, Part part, boolean upgrading)294     private static void addOneAttachment(Context context, EmailContent.Message localMessage,
295             Part part, boolean upgrading) throws MessagingException, IOException {
296 
297         Attachment localAttachment = new Attachment();
298 
299         // Transfer fields from mime format to provider format
300         String contentType = MimeUtility.unfoldAndDecode(part.getContentType());
301         String name = MimeUtility.getHeaderParameter(contentType, "name");
302         if (name == null) {
303             String contentDisposition = MimeUtility.unfoldAndDecode(part.getDisposition());
304             name = MimeUtility.getHeaderParameter(contentDisposition, "filename");
305         }
306 
307         // Select the URI for the new attachments.  For attachments downloaded by the legacy
308         // IMAP/POP code, this is not determined yet, so is null (it will be rewritten below,
309         // or later, when the actual attachment file is created.)
310         //
311         // When upgrading older local accounts, the URI represents a local asset (e.g. a photo)
312         // so we need to preserve the URI.
313         // TODO This works for outgoing messages, where the URI does not change.  May need
314         // additional logic to handle the case of rewriting URI for received attachments.
315         Uri contentUri = null;
316         String contentUriString = null;
317         if (upgrading) {
318             Body body = part.getBody();
319             if (body instanceof LocalStore.LocalAttachmentBody) {
320                 LocalStore.LocalAttachmentBody localBody = (LocalStore.LocalAttachmentBody) body;
321                 contentUri = localBody.getContentUri();
322                 if (contentUri != null) {
323                     contentUriString = contentUri.toString();
324                 }
325             }
326         }
327 
328         // Find size, if available, via a number of techniques:
329         long size = 0;
330         if (upgrading) {
331             // If upgrading a legacy account, the size must be recaptured from the data source
332             if (contentUri != null) {
333                 Cursor metadataCursor = context.getContentResolver().query(contentUri,
334                         ATTACHMENT_META_COLUMNS_PROJECTION, null, null, null);
335                 if (metadataCursor != null) {
336                     try {
337                         if (metadataCursor.moveToFirst()) {
338                             size = metadataCursor.getInt(ATTACHMENT_META_COLUMNS_SIZE);
339                         }
340                     } finally {
341                         metadataCursor.close();
342                     }
343                 }
344             }
345             // TODO: a downloaded legacy attachment - see if the above code works
346         } else {
347             // Incoming attachment: Try to pull size from disposition (if not downloaded yet)
348             String disposition = part.getDisposition();
349             if (disposition != null) {
350                 String s = MimeUtility.getHeaderParameter(disposition, "size");
351                 if (s != null) {
352                     size = Long.parseLong(s);
353                 }
354             }
355         }
356 
357         // Get partId for unloaded IMAP attachments (if any)
358         // This is only provided (and used) when we have structure but not the actual attachment
359         String[] partIds = part.getHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA);
360         String partId = partIds != null ? partIds[0] : null;
361 
362         localAttachment.mFileName = name;
363         localAttachment.mMimeType = part.getMimeType();
364         localAttachment.mSize = size;           // May be reset below if file handled
365         localAttachment.mContentId = part.getContentId();
366         localAttachment.mContentUri = contentUriString;
367         localAttachment.mMessageKey = localMessage.mId;
368         localAttachment.mLocation = partId;
369         localAttachment.mEncoding = "B";        // TODO - convert other known encodings
370 
371         if (DEBUG_ATTACHMENTS) {
372             Log.d(Email.LOG_TAG, "Add attachment " + localAttachment);
373         }
374 
375         // To prevent duplication - do we already have a matching attachment?
376         // The fields we'll check for equality are:
377         //  mFileName, mMimeType, mContentId, mMessageKey, mLocation
378         // NOTE:  This will false-positive if you attach the exact same file, twice, to a POP3
379         // message.  We can live with that - you'll get one of the copies.
380         Uri uri = ContentUris.withAppendedId(Attachment.MESSAGE_ID_URI, localMessage.mId);
381         Cursor cursor = context.getContentResolver().query(uri, Attachment.CONTENT_PROJECTION,
382                 null, null, null);
383         boolean attachmentFoundInDb = false;
384         try {
385             while (cursor.moveToNext()) {
386                 Attachment dbAttachment = new Attachment().restore(cursor);
387                 // We test each of the fields here (instead of in SQL) because they may be
388                 // null, or may be strings.
389                 if (stringNotEqual(dbAttachment.mFileName, localAttachment.mFileName)) continue;
390                 if (stringNotEqual(dbAttachment.mMimeType, localAttachment.mMimeType)) continue;
391                 if (stringNotEqual(dbAttachment.mContentId, localAttachment.mContentId)) continue;
392                 if (stringNotEqual(dbAttachment.mLocation, localAttachment.mLocation)) continue;
393                 // We found a match, so use the existing attachment id, and stop looking/looping
394                 attachmentFoundInDb = true;
395                 localAttachment.mId = dbAttachment.mId;
396                 if (DEBUG_ATTACHMENTS) {
397                     Log.d(Email.LOG_TAG, "Skipped, found db attachment " + dbAttachment);
398                 }
399                 break;
400             }
401         } finally {
402             cursor.close();
403         }
404 
405         // Save the attachment (so far) in order to obtain an id
406         if (!attachmentFoundInDb) {
407             localAttachment.save(context);
408         }
409 
410         // If an attachment body was actually provided, we need to write the file now
411         if (!upgrading) {
412             saveAttachmentBody(context, part, localAttachment, localMessage.mAccountKey);
413         }
414 
415         if (localMessage.mAttachments == null) {
416             localMessage.mAttachments = new ArrayList<Attachment>();
417         }
418         localMessage.mAttachments.add(localAttachment);
419         localMessage.mFlagAttachment = true;
420     }
421 
422     /**
423      * Helper for addOneAttachment that compares two strings, deals with nulls, and treats
424      * nulls and empty strings as equal.
425      */
stringNotEqual(String a, String b)426     /* package */ static boolean stringNotEqual(String a, String b) {
427         if (a == null && b == null) return false;       // fast exit for two null strings
428         if (a == null) a = "";
429         if (b == null) b = "";
430         return !a.equals(b);
431     }
432 
433     /**
434      * Save the body part of a single attachment, to a file in the attachments directory.
435      */
saveAttachmentBody(Context context, Part part, Attachment localAttachment, long accountId)436     public static void saveAttachmentBody(Context context, Part part, Attachment localAttachment,
437             long accountId) throws MessagingException, IOException {
438         if (part.getBody() != null) {
439             long attachmentId = localAttachment.mId;
440 
441             InputStream in = part.getBody().getInputStream();
442 
443             File saveIn = AttachmentProvider.getAttachmentDirectory(context, accountId);
444             if (!saveIn.exists()) {
445                 saveIn.mkdirs();
446             }
447             File saveAs = AttachmentProvider.getAttachmentFilename(context, accountId,
448                     attachmentId);
449             saveAs.createNewFile();
450             FileOutputStream out = new FileOutputStream(saveAs);
451             long copySize = IOUtils.copy(in, out);
452             in.close();
453             out.close();
454 
455             // update the attachment with the extra information we now know
456             String contentUriString = AttachmentProvider.getAttachmentUri(
457                     accountId, attachmentId).toString();
458 
459             localAttachment.mSize = copySize;
460             localAttachment.mContentUri = contentUriString;
461 
462             // update the attachment in the database as well
463             ContentValues cv = new ContentValues();
464             cv.put(AttachmentColumns.SIZE, copySize);
465             cv.put(AttachmentColumns.CONTENT_URI, contentUriString);
466             Uri uri = ContentUris.withAppendedId(Attachment.CONTENT_URI, attachmentId);
467             context.getContentResolver().update(uri, cv, null, null);
468         }
469     }
470 
471     /**
472      * Read a complete Provider message into a legacy message (for IMAP upload).  This
473      * is basically the equivalent of LocalFolder.getMessages() + LocalFolder.fetch().
474      */
makeMessage(Context context, EmailContent.Message localMessage)475     public static Message makeMessage(Context context, EmailContent.Message localMessage)
476             throws MessagingException {
477         MimeMessage message = new MimeMessage();
478 
479         // LocalFolder.getMessages() equivalent:  Copy message fields
480         message.setSubject(localMessage.mSubject == null ? "" : localMessage.mSubject);
481         Address[] from = Address.unpack(localMessage.mFrom);
482         if (from.length > 0) {
483             message.setFrom(from[0]);
484         }
485         message.setSentDate(new Date(localMessage.mTimeStamp));
486         message.setUid(localMessage.mServerId);
487         message.setFlag(Flag.DELETED,
488                 localMessage.mFlagLoaded == EmailContent.Message.FLAG_LOADED_DELETED);
489         message.setFlag(Flag.SEEN, localMessage.mFlagRead);
490         message.setFlag(Flag.FLAGGED, localMessage.mFlagFavorite);
491 //      message.setFlag(Flag.DRAFT, localMessage.mMailboxKey == draftMailboxKey);
492         message.setRecipients(RecipientType.TO, Address.unpack(localMessage.mTo));
493         message.setRecipients(RecipientType.CC, Address.unpack(localMessage.mCc));
494         message.setRecipients(RecipientType.BCC, Address.unpack(localMessage.mBcc));
495         message.setReplyTo(Address.unpack(localMessage.mReplyTo));
496         message.setInternalDate(new Date(localMessage.mServerTimeStamp));
497         message.setMessageId(localMessage.mMessageId);
498 
499         // LocalFolder.fetch() equivalent: build body parts
500         message.setHeader(MimeHeader.HEADER_CONTENT_TYPE, "multipart/mixed");
501         MimeMultipart mp = new MimeMultipart();
502         mp.setSubType("mixed");
503         message.setBody(mp);
504 
505         try {
506             addTextBodyPart(mp, "text/html", null,
507                     EmailContent.Body.restoreBodyHtmlWithMessageId(context, localMessage.mId));
508         } catch (RuntimeException rte) {
509             Log.d(Email.LOG_TAG, "Exception while reading html body " + rte.toString());
510         }
511 
512         try {
513             addTextBodyPart(mp, "text/plain", null,
514                     EmailContent.Body.restoreBodyTextWithMessageId(context, localMessage.mId));
515         } catch (RuntimeException rte) {
516             Log.d(Email.LOG_TAG, "Exception while reading text body " + rte.toString());
517         }
518 
519         boolean isReply = (localMessage.mFlags & EmailContent.Message.FLAG_TYPE_REPLY) != 0;
520         boolean isForward = (localMessage.mFlags & EmailContent.Message.FLAG_TYPE_FORWARD) != 0;
521 
522         // If there is a quoted part (forwarding or reply), add the intro first, and then the
523         // rest of it.  If it is opened in some other viewer, it will (hopefully) be displayed in
524         // the same order as we've just set up the blocks:  composed text, intro, replied text
525         if (isReply || isForward) {
526             try {
527                 addTextBodyPart(mp, "text/plain", BODY_QUOTED_PART_INTRO,
528                         EmailContent.Body.restoreIntroTextWithMessageId(context, localMessage.mId));
529             } catch (RuntimeException rte) {
530                 Log.d(Email.LOG_TAG, "Exception while reading text reply " + rte.toString());
531             }
532 
533             String replyTag = isReply ? BODY_QUOTED_PART_REPLY : BODY_QUOTED_PART_FORWARD;
534             try {
535                 addTextBodyPart(mp, "text/html", replyTag,
536                         EmailContent.Body.restoreReplyHtmlWithMessageId(context, localMessage.mId));
537             } catch (RuntimeException rte) {
538                 Log.d(Email.LOG_TAG, "Exception while reading html reply " + rte.toString());
539             }
540 
541             try {
542                 addTextBodyPart(mp, "text/plain", replyTag,
543                         EmailContent.Body.restoreReplyTextWithMessageId(context, localMessage.mId));
544             } catch (RuntimeException rte) {
545                 Log.d(Email.LOG_TAG, "Exception while reading text reply " + rte.toString());
546             }
547         }
548 
549         // Attachments
550         // TODO: Make sure we deal with these as structures and don't accidentally upload files
551 //        Uri uri = ContentUris.withAppendedId(Attachment.MESSAGE_ID_URI, localMessage.mId);
552 //        Cursor attachments = context.getContentResolver().query(uri, Attachment.CONTENT_PROJECTION,
553 //                null, null, null);
554 //        try {
555 //
556 //        } finally {
557 //            attachments.close();
558 //        }
559 
560         return message;
561     }
562 
563     /**
564      * Helper method to add a body part for a given type of text, if found
565      *
566      * @param mp The text body part will be added to this multipart
567      * @param contentType The content-type of the text being added
568      * @param quotedPartTag If non-null, HEADER_ANDROID_BODY_QUOTED_PART will be set to this value
569      * @param partText The text to add.  If null, nothing happens
570      */
addTextBodyPart(MimeMultipart mp, String contentType, String quotedPartTag, String partText)571     private static void addTextBodyPart(MimeMultipart mp, String contentType, String quotedPartTag,
572             String partText) throws MessagingException {
573         if (partText == null) {
574             return;
575         }
576         TextBody body = new TextBody(partText);
577         MimeBodyPart bp = new MimeBodyPart(body, contentType);
578         if (quotedPartTag != null) {
579             bp.addHeader(MimeHeader.HEADER_ANDROID_BODY_QUOTED_PART, quotedPartTag);
580         }
581         mp.addBodyPart(bp);
582     }
583 
584     /**
585      * Conversion from provider account to legacy account
586      *
587      * Used for backup/restore.
588      *
589      * @param context application context
590      * @param fromAccount the provider account to be backed up (including transient hostauth's)
591      * @return a legacy Account object ready to be committed to preferences
592      */
makeLegacyAccount(Context context, EmailContent.Account fromAccount)593     /* package */ static Account makeLegacyAccount(Context context,
594             EmailContent.Account fromAccount) {
595         Account result = new Account(context);
596 
597         result.setDescription(fromAccount.getDisplayName());
598         result.setEmail(fromAccount.getEmailAddress());
599         // fromAccount.mSyncKey - assume lost if restoring
600         result.setSyncWindow(fromAccount.getSyncLookback());
601         result.setAutomaticCheckIntervalMinutes(fromAccount.getSyncInterval());
602         // fromAccount.mHostAuthKeyRecv - id not saved; will be reassigned when restoring
603         // fromAccount.mHostAuthKeySend - id not saved; will be reassigned when restoring
604 
605         // Provider Account flags, and how they are mapped.
606         //  FLAGS_NOTIFY_NEW_MAIL       -> mNotifyNewMail
607         //  FLAGS_VIBRATE_ALWAYS        -> mVibrate
608         //  FLAGS_VIBRATE_WHEN_SILENT   -> mVibrateWhenSilent
609         //  DELETE_POLICY_NEVER         -> mDeletePolicy
610         //  DELETE_POLICY_7DAYS
611         //  DELETE_POLICY_ON_DELETE
612         result.setNotifyNewMail(0 !=
613             (fromAccount.getFlags() & EmailContent.Account.FLAGS_NOTIFY_NEW_MAIL));
614         result.setVibrate(0 !=
615             (fromAccount.getFlags() & EmailContent.Account.FLAGS_VIBRATE_ALWAYS));
616         result.setVibrateWhenSilent(0 !=
617             (fromAccount.getFlags() & EmailContent.Account.FLAGS_VIBRATE_WHEN_SILENT));
618         result.setDeletePolicy(fromAccount.getDeletePolicy());
619 
620         result.mUuid = fromAccount.getUuid();
621         result.setName(fromAccount.mSenderName);
622         result.setRingtone(fromAccount.mRingtoneUri);
623         result.mProtocolVersion = fromAccount.mProtocolVersion;
624         // int fromAccount.mNewMessageCount = will be reset on next sync
625         result.mSecurityFlags = fromAccount.mSecurityFlags;
626         result.mSignature = fromAccount.mSignature;
627 
628         // Use the existing conversions from HostAuth <-> Uri
629         result.setStoreUri(fromAccount.getStoreUri(context));
630         result.setSenderUri(fromAccount.getSenderUri(context));
631 
632         return result;
633     }
634 
635     /**
636      * Conversion from legacy account to provider account
637      *
638      * Used for backup/restore and for account migration.
639      *
640      * @param context application context
641      * @param fromAccount the legacy account to convert to modern format
642      * @return an Account ready to be committed to provider
643      */
makeAccount(Context context, Account fromAccount)644     public static EmailContent.Account makeAccount(Context context, Account fromAccount) {
645 
646         EmailContent.Account result = new EmailContent.Account();
647 
648         result.setDisplayName(fromAccount.getDescription());
649         result.setEmailAddress(fromAccount.getEmail());
650         result.mSyncKey = null;
651         result.setSyncLookback(fromAccount.getSyncWindow());
652         result.setSyncInterval(fromAccount.getAutomaticCheckIntervalMinutes());
653         // result.mHostAuthKeyRecv;     -- will be set when object is saved
654         // result.mHostAuthKeySend;     -- will be set when object is saved
655         int flags = 0;
656         if (fromAccount.isNotifyNewMail())  flags |= EmailContent.Account.FLAGS_NOTIFY_NEW_MAIL;
657         if (fromAccount.isVibrate())        flags |= EmailContent.Account.FLAGS_VIBRATE_ALWAYS;
658         if (fromAccount.isVibrateWhenSilent())
659             flags |= EmailContent.Account.FLAGS_VIBRATE_WHEN_SILENT;
660         result.setFlags(flags);
661         result.setDeletePolicy(fromAccount.getDeletePolicy());
662         // result.setDefaultAccount();  -- will be set by caller, if neededf
663         result.mCompatibilityUuid = fromAccount.getUuid();
664         result.setSenderName(fromAccount.getName());
665         result.setRingtone(fromAccount.getRingtone());
666         result.mProtocolVersion = fromAccount.mProtocolVersion;
667         result.mNewMessageCount = 0;
668         result.mSecurityFlags = fromAccount.mSecurityFlags;
669         result.mSecuritySyncKey = null;
670         result.mSignature = fromAccount.mSignature;
671 
672         result.setStoreUri(context, fromAccount.getStoreUri());
673         result.setSenderUri(context, fromAccount.getSenderUri());
674 
675         return result;
676     }
677 
678     /**
679      * Conversion from legacy folder to provider mailbox.  Used for account migration.
680      * Note: Many mailbox fields are unused in IMAP & POP accounts.
681      *
682      * @param context application context
683      * @param toAccount the provider account that this folder will be associated with
684      * @param fromFolder the legacy folder to convert to modern format
685      * @return an Account ready to be committed to provider
686      */
makeMailbox(Context context, EmailContent.Account toAccount, Folder fromFolder)687     public static EmailContent.Mailbox makeMailbox(Context context, EmailContent.Account toAccount,
688             Folder fromFolder) throws MessagingException {
689         EmailContent.Mailbox result = new EmailContent.Mailbox();
690 
691         result.mDisplayName = fromFolder.getName();
692         // result.mServerId
693         // result.mParentServerId
694         result.mAccountKey = toAccount.mId;
695         result.mType = inferMailboxTypeFromName(context, fromFolder.getName());
696         // result.mDelimiter
697         // result.mSyncKey
698         // result.mSyncLookback
699         // result.mSyncInterval
700         result.mSyncTime = 0;
701         result.mUnreadCount = fromFolder.getUnreadMessageCount();
702         result.mFlagVisible = true;
703         result.mFlags = 0;
704         result.mVisibleLimit = Email.VISIBLE_LIMIT_DEFAULT;
705         // result.mSyncStatus
706 
707         return result;
708     }
709 
710     /**
711      * Infer mailbox type from mailbox name.  Used by MessagingController (for live folder sync)
712      * and for legacy account upgrades.
713      */
inferMailboxTypeFromName(Context context, String mailboxName)714     public static synchronized int inferMailboxTypeFromName(Context context, String mailboxName) {
715         if (sServerMailboxNames.size() == 0) {
716             // preload the hashmap, one time only
717             sServerMailboxNames.put(
718                     context.getString(R.string.mailbox_name_server_inbox).toLowerCase(),
719                     Mailbox.TYPE_INBOX);
720             sServerMailboxNames.put(
721                     context.getString(R.string.mailbox_name_server_outbox).toLowerCase(),
722                     Mailbox.TYPE_OUTBOX);
723             sServerMailboxNames.put(
724                     context.getString(R.string.mailbox_name_server_drafts).toLowerCase(),
725                     Mailbox.TYPE_DRAFTS);
726             sServerMailboxNames.put(
727                     context.getString(R.string.mailbox_name_server_trash).toLowerCase(),
728                     Mailbox.TYPE_TRASH);
729             sServerMailboxNames.put(
730                     context.getString(R.string.mailbox_name_server_sent).toLowerCase(),
731                     Mailbox.TYPE_SENT);
732             sServerMailboxNames.put(
733                     context.getString(R.string.mailbox_name_server_junk).toLowerCase(),
734                     Mailbox.TYPE_JUNK);
735         }
736         if (mailboxName == null || mailboxName.length() == 0) {
737             return EmailContent.Mailbox.TYPE_MAIL;
738         }
739         String lowerCaseName = mailboxName.toLowerCase();
740         Integer type = sServerMailboxNames.get(lowerCaseName);
741         if (type != null) {
742             return type;
743         }
744         return EmailContent.Mailbox.TYPE_MAIL;
745     }
746 }
747