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