Home | History | Annotate | Download | only in providers
      1 /**
      2  * Copyright (c) 2012, Google Inc.
      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.mail.providers;
     18 
     19 import android.content.AsyncQueryHandler;
     20 import android.content.ContentValues;
     21 import android.content.Context;
     22 import android.database.Cursor;
     23 import android.net.Uri;
     24 import android.os.Parcel;
     25 import android.os.Parcelable;
     26 import android.provider.BaseColumns;
     27 import android.text.Html;
     28 import android.text.SpannableString;
     29 import android.text.TextUtils;
     30 import android.text.util.Linkify;
     31 import android.text.util.Rfc822Token;
     32 import android.text.util.Rfc822Tokenizer;
     33 
     34 import com.android.emailcommon.internet.MimeMessage;
     35 import com.android.emailcommon.internet.MimeUtility;
     36 import com.android.emailcommon.mail.MessagingException;
     37 import com.android.emailcommon.mail.Part;
     38 import com.android.emailcommon.utility.ConversionUtilities;
     39 import com.android.mail.providers.UIProvider.MessageColumns;
     40 import com.android.mail.ui.HtmlMessage;
     41 import com.android.mail.utils.Utils;
     42 import com.google.common.annotations.VisibleForTesting;
     43 import com.google.common.base.Objects;
     44 import com.google.common.collect.Lists;
     45 
     46 import java.util.ArrayList;
     47 import java.util.Collections;
     48 import java.util.List;
     49 import java.util.regex.Pattern;
     50 
     51 
     52 public class Message implements Parcelable, HtmlMessage {
     53     /**
     54      * Regex pattern used to look for any inline images in message bodies, including Gmail-hosted
     55      * relative-URL images, Gmail emoticons, and any external inline images (although we usually
     56      * count on the server to detect external images).
     57      */
     58     private static Pattern INLINE_IMAGE_PATTERN = Pattern.compile("<img\\s+[^>]*src=",
     59             Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
     60 
     61     /**
     62      * @see BaseColumns#_ID
     63      */
     64     public long id;
     65     /**
     66      * @see UIProvider.MessageColumns#SERVER_ID
     67      */
     68     public String serverId;
     69     /**
     70      * @see UIProvider.MessageColumns#URI
     71      */
     72     public Uri uri;
     73     /**
     74      * @see UIProvider.MessageColumns#CONVERSATION_ID
     75      */
     76     public Uri conversationUri;
     77     /**
     78      * @see UIProvider.MessageColumns#SUBJECT
     79      */
     80     public String subject;
     81     /**
     82      * @see UIProvider.MessageColumns#SNIPPET
     83      */
     84     public String snippet;
     85     /**
     86      * @see UIProvider.MessageColumns#FROM
     87      */
     88     private String mFrom;
     89     /**
     90      * @see UIProvider.MessageColumns#TO
     91      */
     92     private String mTo;
     93     /**
     94      * @see UIProvider.MessageColumns#CC
     95      */
     96     private String mCc;
     97     /**
     98      * @see UIProvider.MessageColumns#BCC
     99      */
    100     private String mBcc;
    101     /**
    102      * @see UIProvider.MessageColumns#REPLY_TO
    103      */
    104     private String mReplyTo;
    105     /**
    106      * @see UIProvider.MessageColumns#DATE_RECEIVED_MS
    107      */
    108     public long dateReceivedMs;
    109     /**
    110      * @see UIProvider.MessageColumns#BODY_HTML
    111      */
    112     public String bodyHtml;
    113     /**
    114      * @see UIProvider.MessageColumns#BODY_TEXT
    115      */
    116     public String bodyText;
    117     /**
    118      * @see UIProvider.MessageColumns#EMBEDS_EXTERNAL_RESOURCES
    119      */
    120     public boolean embedsExternalResources;
    121     /**
    122      * @see UIProvider.MessageColumns#REF_MESSAGE_ID
    123      */
    124     public Uri refMessageUri;
    125     /**
    126      * @see UIProvider.MessageColumns#DRAFT_TYPE
    127      */
    128     public int draftType;
    129     /**
    130      * @see UIProvider.MessageColumns#APPEND_REF_MESSAGE_CONTENT
    131      */
    132     public boolean appendRefMessageContent;
    133     /**
    134      * @see UIProvider.MessageColumns#HAS_ATTACHMENTS
    135      */
    136     public boolean hasAttachments;
    137     /**
    138      * @see UIProvider.MessageColumns#ATTACHMENT_LIST_URI
    139      */
    140     public Uri attachmentListUri;
    141     /**
    142      * @see UIProvider.MessageColumns#MESSAGE_FLAGS
    143      */
    144     public long messageFlags;
    145     /**
    146      * @see UIProvider.MessageColumns#ALWAYS_SHOW_IMAGES
    147      */
    148     public boolean alwaysShowImages;
    149     /**
    150      * @see UIProvider.MessageColumns#READ
    151      */
    152     public boolean read;
    153     /**
    154      * @see UIProvider.MessageColumns#SEEN
    155      */
    156     public boolean seen;
    157     /**
    158      * @see UIProvider.MessageColumns#STARRED
    159      */
    160     public boolean starred;
    161     /**
    162      * @see UIProvider.MessageColumns#QUOTE_START_POS
    163      */
    164     public int quotedTextOffset;
    165     /**
    166      * @see UIProvider.MessageColumns#ATTACHMENTS
    167      *<p>
    168      * N.B. this value is NOT immutable and may change during conversation view render.
    169      */
    170     public String attachmentsJson;
    171     /**
    172      * @see UIProvider.MessageColumns#MESSAGE_ACCOUNT_URI
    173      */
    174     public Uri accountUri;
    175     /**
    176      * @see UIProvider.MessageColumns#EVENT_INTENT_URI
    177      */
    178     public Uri eventIntentUri;
    179     /**
    180      * @see UIProvider.MessageColumns#SPAM_WARNING_STRING
    181      */
    182     public String spamWarningString;
    183     /**
    184      * @see UIProvider.MessageColumns#SPAM_WARNING_LEVEL
    185      */
    186     public int spamWarningLevel;
    187     /**
    188      * @see UIProvider.MessageColumns#SPAM_WARNING_LINK_TYPE
    189      */
    190     public int spamLinkType;
    191     /**
    192      * @see UIProvider.MessageColumns#VIA_DOMAIN
    193      */
    194     public String viaDomain;
    195     /**
    196      * @see UIProvider.MessageColumns#IS_SENDING
    197      */
    198     public boolean isSending;
    199 
    200     private transient String[] mFromAddresses = null;
    201     private transient String[] mToAddresses = null;
    202     private transient String[] mCcAddresses = null;
    203     private transient String[] mBccAddresses = null;
    204     private transient String[] mReplyToAddresses = null;
    205 
    206     private transient List<Attachment> mAttachments = null;
    207 
    208     @Override
    209     public int describeContents() {
    210         return 0;
    211     }
    212 
    213     @Override
    214     public boolean equals(Object o) {
    215         return this == o || (o != null && o instanceof Message
    216                 && Objects.equal(uri, ((Message) o).uri));
    217     }
    218 
    219     @Override
    220     public int hashCode() {
    221         return uri == null ? 0 : uri.hashCode();
    222     }
    223 
    224     @Override
    225     public void writeToParcel(Parcel dest, int flags) {
    226         dest.writeLong(id);
    227         dest.writeString(serverId);
    228         dest.writeParcelable(uri, 0);
    229         dest.writeParcelable(conversationUri, 0);
    230         dest.writeString(subject);
    231         dest.writeString(snippet);
    232         dest.writeString(mFrom);
    233         dest.writeString(mTo);
    234         dest.writeString(mCc);
    235         dest.writeString(mBcc);
    236         dest.writeString(mReplyTo);
    237         dest.writeLong(dateReceivedMs);
    238         dest.writeString(bodyHtml);
    239         dest.writeString(bodyText);
    240         dest.writeInt(embedsExternalResources ? 1 : 0);
    241         dest.writeParcelable(refMessageUri, 0);
    242         dest.writeInt(draftType);
    243         dest.writeInt(appendRefMessageContent ? 1 : 0);
    244         dest.writeInt(hasAttachments ? 1 : 0);
    245         dest.writeParcelable(attachmentListUri, 0);
    246         dest.writeLong(messageFlags);
    247         dest.writeInt(alwaysShowImages ? 1 : 0);
    248         dest.writeInt(quotedTextOffset);
    249         dest.writeString(attachmentsJson);
    250         dest.writeParcelable(accountUri, 0);
    251         dest.writeParcelable(eventIntentUri, 0);
    252         dest.writeString(spamWarningString);
    253         dest.writeInt(spamWarningLevel);
    254         dest.writeInt(spamLinkType);
    255         dest.writeString(viaDomain);
    256         dest.writeInt(isSending ? 1 : 0);
    257     }
    258 
    259     private Message(Parcel in) {
    260         id = in.readLong();
    261         serverId = in.readString();
    262         uri = in.readParcelable(null);
    263         conversationUri = in.readParcelable(null);
    264         subject = in.readString();
    265         snippet = in.readString();
    266         mFrom = in.readString();
    267         mTo = in.readString();
    268         mCc = in.readString();
    269         mBcc = in.readString();
    270         mReplyTo = in.readString();
    271         dateReceivedMs = in.readLong();
    272         bodyHtml = in.readString();
    273         bodyText = in.readString();
    274         embedsExternalResources = in.readInt() != 0;
    275         refMessageUri = in.readParcelable(null);
    276         draftType = in.readInt();
    277         appendRefMessageContent = in.readInt() != 0;
    278         hasAttachments = in.readInt() != 0;
    279         attachmentListUri = in.readParcelable(null);
    280         messageFlags = in.readLong();
    281         alwaysShowImages = in.readInt() != 0;
    282         quotedTextOffset = in.readInt();
    283         attachmentsJson = in.readString();
    284         accountUri = in.readParcelable(null);
    285         eventIntentUri = in.readParcelable(null);
    286         spamWarningString = in.readString();
    287         spamWarningLevel = in.readInt();
    288         spamLinkType = in.readInt();
    289         viaDomain = in.readString();
    290         isSending = in.readInt() != 0;
    291     }
    292 
    293     public Message() {
    294 
    295     }
    296 
    297     @Override
    298     public String toString() {
    299         return "[message id=" + id + "]";
    300     }
    301 
    302     public static final Creator<Message> CREATOR = new Creator<Message>() {
    303 
    304         @Override
    305         public Message createFromParcel(Parcel source) {
    306             return new Message(source);
    307         }
    308 
    309         @Override
    310         public Message[] newArray(int size) {
    311             return new Message[size];
    312         }
    313 
    314     };
    315 
    316     public Message(Cursor cursor) {
    317         if (cursor != null) {
    318             id = cursor.getLong(UIProvider.MESSAGE_ID_COLUMN);
    319             serverId = cursor.getString(UIProvider.MESSAGE_SERVER_ID_COLUMN);
    320             final String messageUriStr = cursor.getString(UIProvider.MESSAGE_URI_COLUMN);
    321             uri = !TextUtils.isEmpty(messageUriStr) ? Uri.parse(messageUriStr) : null;
    322             final String convUriStr = cursor.getString(UIProvider.MESSAGE_CONVERSATION_URI_COLUMN);
    323             conversationUri = !TextUtils.isEmpty(convUriStr) ? Uri.parse(convUriStr) : null;
    324             subject = cursor.getString(UIProvider.MESSAGE_SUBJECT_COLUMN);
    325             snippet = cursor.getString(UIProvider.MESSAGE_SNIPPET_COLUMN);
    326             mFrom = cursor.getString(UIProvider.MESSAGE_FROM_COLUMN);
    327             mTo = cursor.getString(UIProvider.MESSAGE_TO_COLUMN);
    328             mCc = cursor.getString(UIProvider.MESSAGE_CC_COLUMN);
    329             mBcc = cursor.getString(UIProvider.MESSAGE_BCC_COLUMN);
    330             mReplyTo = cursor.getString(UIProvider.MESSAGE_REPLY_TO_COLUMN);
    331             dateReceivedMs = cursor.getLong(UIProvider.MESSAGE_DATE_RECEIVED_MS_COLUMN);
    332             bodyHtml = cursor.getString(UIProvider.MESSAGE_BODY_HTML_COLUMN);
    333             bodyText = cursor.getString(UIProvider.MESSAGE_BODY_TEXT_COLUMN);
    334             embedsExternalResources = cursor
    335                     .getInt(UIProvider.MESSAGE_EMBEDS_EXTERNAL_RESOURCES_COLUMN) != 0;
    336             final String refMessageUriStr =
    337                     cursor.getString(UIProvider.MESSAGE_REF_MESSAGE_URI_COLUMN);
    338             refMessageUri = !TextUtils.isEmpty(refMessageUriStr) ?
    339                     Uri.parse(refMessageUriStr) : null;
    340             draftType = cursor.getInt(UIProvider.MESSAGE_DRAFT_TYPE_COLUMN);
    341             appendRefMessageContent = cursor
    342                     .getInt(UIProvider.MESSAGE_APPEND_REF_MESSAGE_CONTENT_COLUMN) != 0;
    343             hasAttachments = cursor.getInt(UIProvider.MESSAGE_HAS_ATTACHMENTS_COLUMN) != 0;
    344             final String attachmentsUri = cursor
    345                     .getString(UIProvider.MESSAGE_ATTACHMENT_LIST_URI_COLUMN);
    346             attachmentListUri = hasAttachments && !TextUtils.isEmpty(attachmentsUri) ? Uri
    347                     .parse(attachmentsUri) : null;
    348             messageFlags = cursor.getLong(UIProvider.MESSAGE_FLAGS_COLUMN);
    349             alwaysShowImages = cursor.getInt(UIProvider.MESSAGE_ALWAYS_SHOW_IMAGES_COLUMN) != 0;
    350             read = cursor.getInt(UIProvider.MESSAGE_READ_COLUMN) != 0;
    351             seen = cursor.getInt(UIProvider.MESSAGE_SEEN_COLUMN) != 0;
    352             starred = cursor.getInt(UIProvider.MESSAGE_STARRED_COLUMN) != 0;
    353             quotedTextOffset = cursor.getInt(UIProvider.QUOTED_TEXT_OFFSET_COLUMN);
    354             attachmentsJson = cursor.getString(UIProvider.MESSAGE_ATTACHMENTS_COLUMN);
    355             String accountUriString = cursor.getString(UIProvider.MESSAGE_ACCOUNT_URI_COLUMN);
    356             accountUri = !TextUtils.isEmpty(accountUriString) ? Uri.parse(accountUriString) : null;
    357             eventIntentUri =
    358                     Utils.getValidUri(cursor.getString(UIProvider.MESSAGE_EVENT_INTENT_COLUMN));
    359             spamWarningString =
    360                     cursor.getString(UIProvider.MESSAGE_SPAM_WARNING_STRING_ID_COLUMN);
    361             spamWarningLevel = cursor.getInt(UIProvider.MESSAGE_SPAM_WARNING_LEVEL_COLUMN);
    362             spamLinkType = cursor.getInt(UIProvider.MESSAGE_SPAM_WARNING_LINK_TYPE_COLUMN);
    363             viaDomain = cursor.getString(UIProvider.MESSAGE_VIA_DOMAIN_COLUMN);
    364             isSending = cursor.getInt(UIProvider.MESSAGE_IS_SENDING_COLUMN) != 0;
    365         }
    366     }
    367 
    368     public Message(Context context, MimeMessage mimeMessage, Uri emlFileUri)
    369             throws MessagingException {
    370         // Set message header values.
    371         setFrom(com.android.emailcommon.mail.Address.pack(mimeMessage.getFrom()));
    372         setTo(com.android.emailcommon.mail.Address.pack(mimeMessage.getRecipients(
    373                 com.android.emailcommon.mail.Message.RecipientType.TO)));
    374         setCc(com.android.emailcommon.mail.Address.pack(mimeMessage.getRecipients(
    375                 com.android.emailcommon.mail.Message.RecipientType.CC)));
    376         setBcc(com.android.emailcommon.mail.Address.pack(mimeMessage.getRecipients(
    377                 com.android.emailcommon.mail.Message.RecipientType.BCC)));
    378         setReplyTo(com.android.emailcommon.mail.Address.pack(mimeMessage.getReplyTo()));
    379         subject = mimeMessage.getSubject();
    380         dateReceivedMs = mimeMessage.getSentDate().getTime();
    381 
    382         // for now, always set defaults
    383         alwaysShowImages = false;
    384         viaDomain = null;
    385         draftType = UIProvider.DraftType.NOT_A_DRAFT;
    386         isSending = false;
    387         starred = false;
    388         spamWarningString = null;
    389         messageFlags = 0;
    390         hasAttachments = false;
    391 
    392         // body values (snippet/bodyText/bodyHtml)
    393         // Now process body parts & attachments
    394         ArrayList<Part> viewables = new ArrayList<Part>();
    395         ArrayList<Part> attachments = new ArrayList<Part>();
    396         MimeUtility.collectParts(mimeMessage, viewables, attachments);
    397 
    398         ConversionUtilities.BodyFieldData data =
    399                 ConversionUtilities.parseBodyFields(viewables);
    400 
    401         snippet = data.snippet;
    402         bodyText = data.textContent;
    403         bodyHtml = data.htmlContent;
    404 
    405         // populate mAttachments
    406         mAttachments = Lists.newArrayList();
    407 
    408         int partId = 0;
    409         final String messageId = mimeMessage.getMessageId();
    410         for (final Part attachmentPart : attachments) {
    411             mAttachments.add(new Attachment(context, attachmentPart,
    412                     emlFileUri, messageId, Integer.toString(partId++)));
    413         }
    414 
    415         hasAttachments = !mAttachments.isEmpty();
    416 
    417         attachmentListUri =  hasAttachments ?
    418                 EmlAttachmentProvider.getAttachmentsListUri(emlFileUri, messageId) : null;
    419     }
    420 
    421     public boolean isFlaggedReplied() {
    422         return (messageFlags & UIProvider.MessageFlags.REPLIED) ==
    423                 UIProvider.MessageFlags.REPLIED;
    424     }
    425 
    426     public boolean isFlaggedForwarded() {
    427         return (messageFlags & UIProvider.MessageFlags.FORWARDED) ==
    428                 UIProvider.MessageFlags.FORWARDED;
    429     }
    430 
    431     public boolean isFlaggedCalendarInvite() {
    432         return (messageFlags & UIProvider.MessageFlags.CALENDAR_INVITE) ==
    433                 UIProvider.MessageFlags.CALENDAR_INVITE;
    434     }
    435 
    436     public String getFrom() {
    437         return mFrom;
    438     }
    439 
    440     public synchronized void setFrom(final String from) {
    441         mFrom = from;
    442         mFromAddresses = null;
    443     }
    444 
    445     public String getTo() {
    446         return mTo;
    447     }
    448 
    449     public synchronized void setTo(final String to) {
    450         mTo = to;
    451         mToAddresses = null;
    452     }
    453 
    454     public String getCc() {
    455         return mCc;
    456     }
    457 
    458     public synchronized void setCc(final String cc) {
    459         mCc = cc;
    460         mCcAddresses = null;
    461     }
    462 
    463     public String getBcc() {
    464         return mBcc;
    465     }
    466 
    467     public synchronized void setBcc(final String bcc) {
    468         mBcc = bcc;
    469         mBccAddresses = null;
    470     }
    471 
    472     @VisibleForTesting
    473     public String getReplyTo() {
    474         return mReplyTo;
    475     }
    476 
    477     public synchronized void setReplyTo(final String replyTo) {
    478         mReplyTo = replyTo;
    479         mReplyToAddresses = null;
    480     }
    481 
    482     public synchronized String[] getFromAddresses() {
    483         if (mFromAddresses == null) {
    484             mFromAddresses = tokenizeAddresses(mFrom);
    485         }
    486         return mFromAddresses;
    487     }
    488 
    489     public String[] getFromAddressesUnescaped() {
    490         return unescapeAddresses(getFromAddresses());
    491     }
    492 
    493     public synchronized String[] getToAddresses() {
    494         if (mToAddresses == null) {
    495             mToAddresses = tokenizeAddresses(mTo);
    496         }
    497         return mToAddresses;
    498     }
    499 
    500     public String[] getToAddressesUnescaped() {
    501         return unescapeAddresses(getToAddresses());
    502     }
    503 
    504     public synchronized String[] getCcAddresses() {
    505         if (mCcAddresses == null) {
    506             mCcAddresses = tokenizeAddresses(mCc);
    507         }
    508         return mCcAddresses;
    509     }
    510 
    511     public String[] getCcAddressesUnescaped() {
    512         return unescapeAddresses(getCcAddresses());
    513     }
    514 
    515     public synchronized String[] getBccAddresses() {
    516         if (mBccAddresses == null) {
    517             mBccAddresses = tokenizeAddresses(mBcc);
    518         }
    519         return mBccAddresses;
    520     }
    521 
    522     public String[] getBccAddressesUnescaped() {
    523         return unescapeAddresses(getBccAddresses());
    524     }
    525 
    526     public synchronized String[] getReplyToAddresses() {
    527         if (mReplyToAddresses == null) {
    528             mReplyToAddresses = tokenizeAddresses(mReplyTo);
    529         }
    530         return mReplyToAddresses;
    531     }
    532 
    533     public String[] getReplyToAddressesUnescaped() {
    534         return unescapeAddresses(getReplyToAddresses());
    535     }
    536 
    537     private static String[] unescapeAddresses(String[] escaped) {
    538         final String[] unescaped = new String[escaped.length];
    539         for (int i = 0; i < escaped.length; i++) {
    540             final String escapeMore = escaped[i].replace("<", "&lt;").replace(">", "&gt;");
    541             unescaped[i] = Html.fromHtml(escapeMore).toString();
    542         }
    543         return unescaped;
    544     }
    545 
    546     public static String[] tokenizeAddresses(String addresses) {
    547         if (TextUtils.isEmpty(addresses)) {
    548             return new String[0];
    549         }
    550 
    551         Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(addresses);
    552         String[] strings = new String[tokens.length];
    553         for (int i = 0; i < tokens.length;i++) {
    554             strings[i] = tokens[i].toString();
    555         }
    556         return strings;
    557     }
    558 
    559     public List<Attachment> getAttachments() {
    560         if (mAttachments == null) {
    561             if (attachmentsJson != null) {
    562                 mAttachments = Attachment.fromJSONArray(attachmentsJson);
    563             } else {
    564                 mAttachments = Collections.emptyList();
    565             }
    566         }
    567         return mAttachments;
    568     }
    569 
    570     /**
    571      * Returns whether a "Show Pictures" button should initially appear for this message. If the
    572      * button is shown, the message must also block all non-local images in the body. Inversely, if
    573      * the button is not shown, the message must show all images within (or else the user would be
    574      * stuck with no images and no way to reveal them).
    575      *
    576      * @return true if a "Show Pictures" button should appear.
    577      */
    578     public boolean shouldShowImagePrompt() {
    579         return !alwaysShowImages && (embedsExternalResources ||
    580                 (!TextUtils.isEmpty(bodyHtml) && INLINE_IMAGE_PATTERN.matcher(bodyHtml).find()));
    581     }
    582 
    583     @Override
    584     public boolean embedsExternalResources() {
    585         return embedsExternalResources;
    586     }
    587 
    588     /**
    589      * Helper method to command a provider to mark all messages from this sender with the
    590      * {@link MessageColumns#ALWAYS_SHOW_IMAGES} flag set.
    591      *
    592      * @param handler a caller-provided handler to run the query on
    593      * @param token (optional) token to identify the command to the handler
    594      * @param cookie (optional) cookie to pass to the handler
    595      */
    596     public void markAlwaysShowImages(AsyncQueryHandler handler, int token, Object cookie) {
    597         alwaysShowImages = true;
    598 
    599         final ContentValues values = new ContentValues(1);
    600         values.put(UIProvider.MessageColumns.ALWAYS_SHOW_IMAGES, 1);
    601 
    602         handler.startUpdate(token, cookie, uri, values, null, null);
    603     }
    604 
    605     @Override
    606     public String getBodyAsHtml() {
    607         String body = "";
    608         if (!TextUtils.isEmpty(bodyHtml)) {
    609             body = bodyHtml;
    610         } else if (!TextUtils.isEmpty(bodyText)) {
    611             final SpannableString spannable = new SpannableString(bodyText);
    612             Linkify.addLinks(spannable, Linkify.EMAIL_ADDRESSES);
    613             body = Html.toHtml(spannable);
    614         }
    615         return body;
    616     }
    617 
    618     @Override
    619     public long getId() {
    620         return id;
    621     }
    622 }
    623