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