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