Conversation.java revision 192fac189e6aed434556a4e37bd3c5c29ef02f29
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        try {
186            dest.writeString(ConversationInfo.toString(conversationInfo));
187        } catch (JSONException e) {
188            LogUtils.d(LOG_TAG, e, "Error adding conversationinfo to parcel");
189        }
190    }
191
192    private Conversation(Parcel in) {
193        id = in.readLong();
194        uri = in.readParcelable(null);
195        subject = in.readString();
196        dateMs = in.readLong();
197        snippet = in.readString();
198        hasAttachments = (in.readByte() != 0);
199        messageListUri = in.readParcelable(null);
200        senders = in.readString();
201        numMessages = in.readInt();
202        numDrafts = in.readInt();
203        sendingState = in.readInt();
204        priority = in.readInt();
205        read = (in.readByte() != 0);
206        starred = (in.readByte() != 0);
207        folderList = in.readString();
208        rawFolders = in.readString();
209        convFlags = in.readInt();
210        personalLevel = in.readInt();
211        spam = in.readInt() != 0;
212        phishing = in.readInt() != 0;
213        muted = in.readInt() != 0;
214        color = in.readInt();
215        accountUri = in.readParcelable(null);
216        position = NO_POSITION;
217        localDeleteOnUpdate = false;
218        try {
219            conversationInfo = ConversationInfo.fromString(in.readString());
220        } catch (JSONException e) {
221            LogUtils.d(LOG_TAG, e, "Error retrieving conversation info from parcel");
222        }
223    }
224
225    @Override
226    public String toString() {
227        return "[conversation id=" + id + ", subject =" + subject + "]";
228    }
229
230    public static final Creator<Conversation> CREATOR = new Creator<Conversation>() {
231
232        @Override
233        public Conversation createFromParcel(Parcel source) {
234            return new Conversation(source);
235        }
236
237        @Override
238        public Conversation[] newArray(int size) {
239            return new Conversation[size];
240        }
241
242    };
243
244    public static final Uri MOVE_CONVERSATIONS_URI = Uri.parse("content://moveconversations");
245
246    /**
247     * The columns that need to be updated to change the read state of a conversation.
248     */
249    public static final String[] UPDATE_FOLDER_COLUMNS = new String[] {
250            ConversationColumns.FOLDER_LIST, ConversationColumns.RAW_FOLDERS
251    };
252
253    private static final String LOG_TAG = LogTag.getLogTag();
254
255    public Conversation(Cursor cursor) {
256        if (cursor != null) {
257            id = cursor.getLong(UIProvider.CONVERSATION_ID_COLUMN);
258            uri = Uri.parse(cursor.getString(UIProvider.CONVERSATION_URI_COLUMN));
259            dateMs = cursor.getLong(UIProvider.CONVERSATION_DATE_RECEIVED_MS_COLUMN);
260            subject = cursor.getString(UIProvider.CONVERSATION_SUBJECT_COLUMN);
261            // Don't allow null subject
262            if (subject == null) {
263                subject = "";
264            }
265            snippet = cursor.getString(UIProvider.CONVERSATION_SNIPPET_COLUMN);
266            hasAttachments = cursor.getInt(UIProvider.CONVERSATION_HAS_ATTACHMENTS_COLUMN) != 0;
267            String messageList = cursor.getString(UIProvider.CONVERSATION_MESSAGE_LIST_URI_COLUMN);
268            messageListUri = !TextUtils.isEmpty(messageList) ? Uri.parse(messageList) : null;
269            numMessages = cursor.getInt(UIProvider.CONVERSATION_NUM_MESSAGES_COLUMN);
270            numDrafts = cursor.getInt(UIProvider.CONVERSATION_NUM_DRAFTS_COLUMN);
271            sendingState = cursor.getInt(UIProvider.CONVERSATION_SENDING_STATE_COLUMN);
272            priority = cursor.getInt(UIProvider.CONVERSATION_PRIORITY_COLUMN);
273            read = cursor.getInt(UIProvider.CONVERSATION_READ_COLUMN) != 0;
274            starred = cursor.getInt(UIProvider.CONVERSATION_STARRED_COLUMN) != 0;
275            folderList = cursor.getString(UIProvider.CONVERSATION_FOLDER_LIST_COLUMN);
276            rawFolders = cursor.getString(UIProvider.CONVERSATION_RAW_FOLDERS_COLUMN);
277            convFlags = cursor.getInt(UIProvider.CONVERSATION_FLAGS_COLUMN);
278            personalLevel = cursor.getInt(UIProvider.CONVERSATION_PERSONAL_LEVEL_COLUMN);
279            spam = cursor.getInt(UIProvider.CONVERSATION_IS_SPAM_COLUMN) != 0;
280            phishing = cursor.getInt(UIProvider.CONVERSATION_IS_PHISHING_COLUMN) != 0;
281            muted = cursor.getInt(UIProvider.CONVERSATION_MUTED_COLUMN) != 0;
282            color = cursor.getInt(UIProvider.CONVERSATION_COLOR_COLUMN);
283            String account = cursor.getString(UIProvider.CONVERSATION_ACCOUNT_URI_COLUMN);
284            accountUri = !TextUtils.isEmpty(account) ? Uri.parse(account) : null;
285            position = NO_POSITION;
286            localDeleteOnUpdate = false;
287            senders = cursor.getString(UIProvider.CONVERSATION_SENDER_INFO_COLUMN);
288            try {
289                conversationInfo = ConversationInfo.fromString(cursor
290                        .getString(UIProvider.CONVERSATION_INFO_COLUMN));
291            } catch (JSONException e) {
292                LogUtils.w(LOG_TAG, e,
293                        "Unable to instantiate ConversationInfo. Try to continue anyway");
294            }
295        }
296    }
297
298    public Conversation() {
299    }
300
301    public static Conversation create(long id, Uri uri, String subject, long dateMs,
302            String snippet, boolean hasAttachment, Uri messageListUri, String senders,
303            int numMessages, int numDrafts, int sendingState, int priority, boolean read,
304            boolean starred, String folderList, String rawFolders, int convFlags,
305            int personalLevel, boolean spam, boolean phishing, boolean muted, Uri accountUri,
306            ConversationInfo conversationInfo) {
307
308        final Conversation conversation = new Conversation();
309
310        conversation.id = id;
311        conversation.uri = uri;
312        conversation.subject = subject;
313        conversation.dateMs = dateMs;
314        conversation.snippet = snippet;
315        conversation.hasAttachments = hasAttachment;
316        conversation.messageListUri = messageListUri;
317        conversation.senders = senders;
318        conversation.numMessages = numMessages;
319        conversation.numDrafts = numDrafts;
320        conversation.sendingState = sendingState;
321        conversation.priority = priority;
322        conversation.read = read;
323        conversation.starred = starred;
324        conversation.folderList = folderList;
325        conversation.rawFolders = rawFolders;
326        conversation.convFlags = convFlags;
327        conversation.personalLevel = personalLevel;
328        conversation.spam = spam;
329        conversation.phishing = phishing;
330        conversation.muted = muted;
331        conversation.color = 0;
332        conversation.accountUri = accountUri;
333        conversation.conversationInfo = conversationInfo;
334        return conversation;
335    }
336
337    @Override
338    public boolean equals(Object o) {
339        if (o instanceof Conversation) {
340            Conversation conv = (Conversation) o;
341            return conv.uri.equals(uri);
342        }
343        return false;
344    }
345
346    @Override
347    public int hashCode() {
348        return uri.hashCode();
349    }
350
351    /**
352     * Get if this conversation is marked as high priority.
353     */
354    public boolean isImportant() {
355        return priority == UIProvider.ConversationPriority.IMPORTANT;
356    }
357
358    /**
359     * Get if this conversation is mostly dead
360     */
361    public boolean isMostlyDead() {
362        return (convFlags & FLAG_MOSTLY_DEAD) != 0;
363    }
364
365    /**
366     * Returns true if the URI of the conversation specified as the needle was
367     * found in the collection of conversations specified as the haystack. False
368     * otherwise. This method is safe to call with null arguments.
369     *
370     * @param haystack
371     * @param needle
372     * @return true if the needle was found in the haystack, false otherwise.
373     */
374    public final static boolean contains(Collection<Conversation> haystack, Conversation needle) {
375        // If the haystack is empty, it cannot contain anything.
376        if (haystack == null || haystack.size() <= 0) {
377            return false;
378        }
379        // The null folder exists everywhere.
380        if (needle == null) {
381            return true;
382        }
383        final long toFind = needle.id;
384        for (final Conversation c : haystack) {
385            if (toFind == c.id) {
386                return true;
387            }
388        }
389        return false;
390    }
391
392    /**
393     * Returns a collection of a single conversation. This method always returns
394     * a valid collection even if the input conversation is null.
395     *
396     * @param in a conversation, possibly null.
397     * @return a collection of the conversation.
398     */
399    public static Collection<Conversation> listOf(Conversation in) {
400        final Collection<Conversation> target = (in == null) ? EMPTY : ImmutableList.of(in);
401        return target;
402    }
403
404    /**
405     * Get the snippet for this conversation. Masks that it may come from
406     * conversation info or the original deprecated snippet string.
407     */
408    public String getSnippet() {
409        return conversationInfo != null && !TextUtils.isEmpty(conversationInfo.firstSnippet) ?
410                conversationInfo.firstSnippet : snippet;
411    }
412
413    /**
414     * Create a human-readable string of all the conversations
415     *
416     * @param collection Any collection of conversations
417     * @return string with a human readable representation of the conversations.
418     */
419    public static String toString(Collection<Conversation> collection) {
420        final StringBuilder out = new StringBuilder(collection.size() + " conversations:");
421        int count = 0;
422        for (final Conversation c : collection) {
423            count++;
424            // Indent the conversations to make them easy to read in debug
425            // output.
426            out.append("      " + count + ": " + c.toString() + "\n");
427        }
428        return out.toString();
429    }
430}
431