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