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