Conversation.java revision d97782206889a5508279b75cce19d7c0fb8a3bca
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.database.Cursor;
20import android.net.Uri;
21import android.os.Parcel;
22import android.os.Parcelable;
23import android.provider.BaseColumns;
24import android.text.TextUtils;
25
26import com.android.mail.providers.UIProvider.ConversationColumns;
27import com.android.mail.utils.LogTag;
28import com.android.mail.utils.LogUtils;
29import com.google.common.collect.ImmutableList;
30
31import org.json.JSONException;
32
33import java.util.Collection;
34import java.util.Collections;
35
36public class Conversation implements Parcelable {
37    public static final int NO_POSITION = -1;
38
39    /**
40     * @see BaseColumns#_ID
41     */
42    public long id;
43    /**
44     * @see UIProvider.ConversationColumns#URI
45     */
46    public Uri uri;
47    /**
48     * @see UIProvider.ConversationColumns#SUBJECT
49     */
50    public String subject;
51    /**
52     * @see UIProvider.ConversationColumns#DATE_RECEIVED_MS
53     */
54    public long dateMs;
55    /**
56     * @see UIProvider.ConversationColumns#SNIPPET
57     */
58    @Deprecated
59    public String snippet;
60    /**
61     * @see UIProvider.ConversationColumns#HAS_ATTACHMENTS
62     */
63    public boolean hasAttachments;
64    /**
65     * @see UIProvider.ConversationColumns#MESSAGE_LIST_URI
66     */
67    public Uri messageListUri;
68    /**
69     * @see UIProvider.ConversationColumns#SENDER_INFO
70     */
71    @Deprecated
72    public String senders;
73    /**
74     * @see UIProvider.ConversationColumns#NUM_MESSAGES
75     */
76    public int numMessages;
77    /**
78     * @see UIProvider.ConversationColumns#NUM_DRAFTS
79     */
80    public int numDrafts;
81    /**
82     * @see UIProvider.ConversationColumns#SENDING_STATE
83     */
84    public int sendingState;
85    /**
86     * @see UIProvider.ConversationColumns#PRIORITY
87     */
88    public int priority;
89    /**
90     * @see UIProvider.ConversationColumns#READ
91     */
92    public boolean read;
93    /**
94     * @see UIProvider.ConversationColumns#STARRED
95     */
96    public boolean starred;
97    /**
98     * @see UIProvider.ConversationColumns#FOLDER_LIST
99     */
100    public String folderList;
101    /**
102     * @see UIProvider.ConversationColumns#RAW_FOLDERS
103     */
104    public String rawFolders;
105    /**
106     * @see UIProvider.ConversationColumns#FLAGS
107     */
108    public int convFlags;
109    /**
110     * @see UIProvider.ConversationColumns#PERSONAL_LEVEL
111     */
112    public int personalLevel;
113    /**
114     * @see UIProvider.ConversationColumns#SPAM
115     */
116    public boolean spam;
117    /**
118     * @see UIProvider.ConversationColumns#MUTED
119     */
120    public boolean muted;
121    /**
122     * @see UIProvider.ConversationColumns#PHISHING
123     */
124    public boolean phishing;
125    /**
126     * @see UIProvider.ConversationColumns#COLOR
127     */
128    public int color;
129    /**
130     * @see UIProvider.ConversationColumns#ACCOUNT_URI
131     */
132    public Uri accountUri;
133    /**
134     * @see UIProvider.ConversationColumns#CONVERSATION_INFO
135     */
136    public ConversationInfo conversationInfo;
137
138    // Used within the UI to indicate the adapter position of this conversation
139    public transient int position;
140    // Used within the UI to indicate that a Conversation should be removed from
141    // the ConversationCursor when executing an update, e.g. the the
142    // Conversation is no longer in the ConversationList for the current folder,
143    // that is it's now in some other folder(s)
144    public transient boolean localDeleteOnUpdate;
145
146    // Constituents of convFlags below
147    // Flag indicating that the item has been deleted, but will continue being
148    // shown in the list Delete/Archive of a mostly-dead item will NOT propagate
149    // the delete/archive, but WILL remove the item from the cursor
150    public static final int FLAG_MOSTLY_DEAD = 1 << 0;
151
152    /** An immutable, empty conversation list */
153    public static final Collection<Conversation> EMPTY = Collections.emptyList();
154
155    @Override
156    public int describeContents() {
157        return 0;
158    }
159
160    @Override
161    public void writeToParcel(Parcel dest, int flags) {
162        dest.writeLong(id);
163        dest.writeParcelable(uri, flags);
164        dest.writeString(subject);
165        dest.writeLong(dateMs);
166        dest.writeString(snippet);
167        dest.writeByte(hasAttachments ? (byte) 1 : 0);
168        dest.writeParcelable(messageListUri, 0);
169        dest.writeString(senders);
170        dest.writeInt(numMessages);
171        dest.writeInt(numDrafts);
172        dest.writeInt(sendingState);
173        dest.writeInt(priority);
174        dest.writeByte(read ? (byte) 1 : 0);
175        dest.writeByte(starred ? (byte) 1 : 0);
176        dest.writeString(folderList);
177        dest.writeString(rawFolders);
178        dest.writeInt(convFlags);
179        dest.writeInt(personalLevel);
180        dest.writeInt(spam ? 1 : 0);
181        dest.writeInt(phishing ? 1 : 0);
182        dest.writeInt(muted ? 1 : 0);
183        dest.writeInt(color);
184        dest.writeParcelable(accountUri, 0);
185    }
186
187    private Conversation(Parcel in) {
188        id = in.readLong();
189        uri = in.readParcelable(null);
190        subject = in.readString();
191        dateMs = in.readLong();
192        snippet = in.readString();
193        hasAttachments = (in.readByte() != 0);
194        messageListUri = in.readParcelable(null);
195        senders = in.readString();
196        numMessages = in.readInt();
197        numDrafts = in.readInt();
198        sendingState = in.readInt();
199        priority = in.readInt();
200        read = (in.readByte() != 0);
201        starred = (in.readByte() != 0);
202        folderList = in.readString();
203        rawFolders = in.readString();
204        convFlags = in.readInt();
205        personalLevel = in.readInt();
206        spam = in.readInt() != 0;
207        phishing = in.readInt() != 0;
208        muted = in.readInt() != 0;
209        color = in.readInt();
210        accountUri = in.readParcelable(null);
211        position = NO_POSITION;
212        localDeleteOnUpdate = false;
213    }
214
215    @Override
216    public String toString() {
217        return "[conversation id=" + id + ", subject =" + subject + "]";
218    }
219
220    public static final Creator<Conversation> CREATOR = new Creator<Conversation>() {
221
222        @Override
223        public Conversation createFromParcel(Parcel source) {
224            return new Conversation(source);
225        }
226
227        @Override
228        public Conversation[] newArray(int size) {
229            return new Conversation[size];
230        }
231
232    };
233
234    public static final Uri MOVE_CONVERSATIONS_URI = Uri.parse("content://moveconversations");
235
236    /**
237     * The columns that need to be updated to change the read state of a conversation.
238     */
239    public static final String[] UPDATE_FOLDER_COLUMNS = new String[] {
240            ConversationColumns.FOLDER_LIST, ConversationColumns.RAW_FOLDERS
241    };
242
243    private static final String LOG_TAG = LogTag.getLogTag();
244
245    public Conversation(Cursor cursor) {
246        if (cursor != null) {
247            id = cursor.getLong(UIProvider.CONVERSATION_ID_COLUMN);
248            uri = Uri.parse(cursor.getString(UIProvider.CONVERSATION_URI_COLUMN));
249            dateMs = cursor.getLong(UIProvider.CONVERSATION_DATE_RECEIVED_MS_COLUMN);
250            subject = cursor.getString(UIProvider.CONVERSATION_SUBJECT_COLUMN);
251            // Don't allow null subject
252            if (subject == null) {
253                subject = "";
254            }
255            snippet = cursor.getString(UIProvider.CONVERSATION_SNIPPET_COLUMN);
256            hasAttachments = cursor.getInt(UIProvider.CONVERSATION_HAS_ATTACHMENTS_COLUMN) != 0;
257            String messageList = cursor.getString(UIProvider.CONVERSATION_MESSAGE_LIST_URI_COLUMN);
258            messageListUri = !TextUtils.isEmpty(messageList) ? Uri.parse(messageList) : null;
259            numMessages = cursor.getInt(UIProvider.CONVERSATION_NUM_MESSAGES_COLUMN);
260            numDrafts = cursor.getInt(UIProvider.CONVERSATION_NUM_DRAFTS_COLUMN);
261            sendingState = cursor.getInt(UIProvider.CONVERSATION_SENDING_STATE_COLUMN);
262            priority = cursor.getInt(UIProvider.CONVERSATION_PRIORITY_COLUMN);
263            read = cursor.getInt(UIProvider.CONVERSATION_READ_COLUMN) != 0;
264            starred = cursor.getInt(UIProvider.CONVERSATION_STARRED_COLUMN) != 0;
265            folderList = cursor.getString(UIProvider.CONVERSATION_FOLDER_LIST_COLUMN);
266            rawFolders = cursor.getString(UIProvider.CONVERSATION_RAW_FOLDERS_COLUMN);
267            convFlags = cursor.getInt(UIProvider.CONVERSATION_FLAGS_COLUMN);
268            personalLevel = cursor.getInt(UIProvider.CONVERSATION_PERSONAL_LEVEL_COLUMN);
269            spam = cursor.getInt(UIProvider.CONVERSATION_IS_SPAM_COLUMN) != 0;
270            phishing = cursor.getInt(UIProvider.CONVERSATION_IS_PHISHING_COLUMN) != 0;
271            muted = cursor.getInt(UIProvider.CONVERSATION_MUTED_COLUMN) != 0;
272            color = cursor.getInt(UIProvider.CONVERSATION_COLOR_COLUMN);
273            String account = cursor.getString(UIProvider.CONVERSATION_ACCOUNT_URI_COLUMN);
274            accountUri = !TextUtils.isEmpty(account) ? Uri.parse(account) : null;
275            position = NO_POSITION;
276            localDeleteOnUpdate = false;
277            senders = cursor.getString(UIProvider.CONVERSATION_SENDER_INFO_COLUMN);
278            try {
279                conversationInfo = ConversationInfo.fromString(cursor
280                        .getString(UIProvider.CONVERSATION_INFO_COLUMN));
281            } catch (JSONException e) {
282                LogUtils.w(LOG_TAG, e,
283                        "Unable to instantiate ConversationInfo. Try to continue anyway");
284            }
285        }
286    }
287
288    public Conversation() {
289    }
290
291    public static Conversation create(long id, Uri uri, String subject, long dateMs,
292            String snippet, boolean hasAttachment, Uri messageListUri, String senders,
293            int numMessages, int numDrafts, int sendingState, int priority, boolean read,
294            boolean starred, String folderList, String rawFolders, int convFlags,
295            int personalLevel, boolean spam, boolean phishing, boolean muted, Uri accountUri) {
296
297        final Conversation conversation = new Conversation();
298
299        conversation.id = id;
300        conversation.uri = uri;
301        conversation.subject = subject;
302        conversation.dateMs = dateMs;
303        conversation.snippet = snippet;
304        conversation.hasAttachments = hasAttachment;
305        conversation.messageListUri = messageListUri;
306        conversation.senders = senders;
307        conversation.numMessages = numMessages;
308        conversation.numDrafts = numDrafts;
309        conversation.sendingState = sendingState;
310        conversation.priority = priority;
311        conversation.read = read;
312        conversation.starred = starred;
313        conversation.folderList = folderList;
314        conversation.rawFolders = rawFolders;
315        conversation.convFlags = convFlags;
316        conversation.personalLevel = personalLevel;
317        conversation.spam = spam;
318        conversation.phishing = phishing;
319        conversation.muted = muted;
320        conversation.color = 0;
321        conversation.accountUri = accountUri;
322        return conversation;
323    }
324
325    @Override
326    public boolean equals(Object o) {
327        if (o instanceof Conversation) {
328            Conversation conv = (Conversation) o;
329            return conv.uri.equals(uri);
330        }
331        return false;
332    }
333
334    @Override
335    public int hashCode() {
336        return uri.hashCode();
337    }
338
339    /**
340     * Get if this conversation is marked as high priority.
341     */
342    public boolean isImportant() {
343        return priority == UIProvider.ConversationPriority.IMPORTANT;
344    }
345
346    /**
347     * Get if this conversation is mostly dead
348     */
349    public boolean isMostlyDead() {
350        return (convFlags & FLAG_MOSTLY_DEAD) != 0;
351    }
352
353    /**
354     * Returns true if the URI of the conversation specified as the needle was
355     * found in the collection of conversations specified as the haystack. False
356     * otherwise. This method is safe to call with nullarguments.
357     *
358     * @param haystack
359     * @param needle
360     * @return true if the needle was found in the haystack, false otherwise.
361     */
362    public final static boolean contains(Collection<Conversation> haystack, Conversation needle) {
363        // If the haystack is empty, it cannot contain anything.
364        if (haystack == null || haystack.size() <= 0) {
365            return false;
366        }
367        // The null folder exists everywhere.
368        if (needle == null) {
369            return true;
370        }
371        final long toFind = needle.id;
372        for (final Conversation c : haystack) {
373            if (toFind == c.id) {
374                return true;
375            }
376        }
377        return false;
378    }
379
380    /**
381     * Returns a collection of a single conversation. This method always returns
382     * a valid collection even if the input conversation is null.
383     *
384     * @param in a conversation, possibly null.
385     * @return a collection of the conversation.
386     */
387    public static Collection<Conversation> listOf(Conversation in) {
388        final Collection<Conversation> target = (in == null) ? EMPTY : ImmutableList.of(in);
389        return target;
390    }
391
392    /**
393     * Get the snippet for this conversation. Masks that it may come from
394     * conversation info or the original deprecated snippet string.
395     */
396    public String getSnippet() {
397        return conversationInfo != null && !TextUtils.isEmpty(conversationInfo.firstSnippet) ?
398                conversationInfo.firstSnippet : snippet;
399    }
400
401    /**
402     * Create a human-readable string of all the conversations
403     *
404     * @param collection Any collection of conversations
405     * @return string with a human readable representation of the conversations.
406     */
407    public static String toString(Collection<Conversation> collection) {
408        final StringBuilder out = new StringBuilder(collection.size() + " conversations:");
409        int count = 0;
410        for (final Conversation c : collection) {
411            count++;
412            // Indent the conversations to make them easy to read in debug
413            // output.
414            out.append("      " + count + ": " + c.toString() + "\n");
415        }
416        return out.toString();
417    }
418}
419