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