Conversation.java revision cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104
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     * @see UIProvider.ConversationColumns#CONVERSATION_BASE_URI
134     */
135    public Uri conversationBaseUri;
136    /**
137     * @see UIProvider.ConversationColumns#CONVERSATION_COOKIE
138     */
139    public String conversationCookie;
140    /**
141     * @see UIProvider.ConversationColumns#REMOTE
142     */
143    public boolean isRemote;
144
145    // Used within the UI to indicate the adapter position of this conversation
146    public transient int position;
147    // Used within the UI to indicate that a Conversation should be removed from
148    // the ConversationCursor when executing an update, e.g. the the
149    // Conversation is no longer in the ConversationList for the current folder,
150    // that is it's now in some other folder(s)
151    public transient boolean localDeleteOnUpdate;
152
153    private ArrayList<Folder> cachedRawFolders;
154    private ArrayList<Folder> cachedDisplayableFolders;
155
156    // Constituents of convFlags below
157    // Flag indicating that the item has been deleted, but will continue being
158    // shown in the list Delete/Archive of a mostly-dead item will NOT propagate
159    // the delete/archive, but WILL remove the item from the cursor
160    public static final int FLAG_MOSTLY_DEAD = 1 << 0;
161
162    /** An immutable, empty conversation list */
163    public static final Collection<Conversation> EMPTY = Collections.emptyList();
164
165    @Override
166    public int describeContents() {
167        return 0;
168    }
169
170    @Override
171    public void writeToParcel(Parcel dest, int flags) {
172        dest.writeLong(id);
173        dest.writeParcelable(uri, flags);
174        dest.writeString(subject);
175        dest.writeLong(dateMs);
176        dest.writeString(snippet);
177        dest.writeInt(hasAttachments ? 1 : 0);
178        dest.writeParcelable(messageListUri, 0);
179        dest.writeString(senders);
180        dest.writeInt(numMessages);
181        dest.writeInt(numDrafts);
182        dest.writeInt(sendingState);
183        dest.writeInt(priority);
184        dest.writeInt(read ? 1 : 0);
185        dest.writeInt(starred ? 1 : 0);
186        dest.writeString(rawFolders);
187        dest.writeInt(convFlags);
188        dest.writeInt(personalLevel);
189        dest.writeInt(spam ? 1 : 0);
190        dest.writeInt(phishing ? 1 : 0);
191        dest.writeInt(muted ? 1 : 0);
192        dest.writeInt(color);
193        dest.writeParcelable(accountUri, 0);
194        dest.writeString(ConversationInfo.toString(conversationInfo));
195        dest.writeParcelable(conversationBaseUri, 0);
196        dest.writeString(conversationCookie);
197        dest.writeInt(isRemote ? 1 : 0);
198    }
199
200    private Conversation(Parcel in) {
201        id = in.readLong();
202        uri = in.readParcelable(null);
203        subject = in.readString();
204        dateMs = in.readLong();
205        snippet = in.readString();
206        hasAttachments = (in.readInt() != 0);
207        messageListUri = in.readParcelable(null);
208        senders = emptyIfNull(in.readString());
209        numMessages = in.readInt();
210        numDrafts = in.readInt();
211        sendingState = in.readInt();
212        priority = in.readInt();
213        read = (in.readInt() != 0);
214        starred = (in.readInt() != 0);
215        rawFolders = in.readString();
216        convFlags = in.readInt();
217        personalLevel = in.readInt();
218        spam = in.readInt() != 0;
219        phishing = in.readInt() != 0;
220        muted = in.readInt() != 0;
221        color = in.readInt();
222        accountUri = in.readParcelable(null);
223        position = NO_POSITION;
224        localDeleteOnUpdate = false;
225        conversationInfo = ConversationInfo.fromString(in.readString());
226        conversationBaseUri = in.readParcelable(null);
227        conversationCookie = in.readString();
228        isRemote = in.readInt() != 0;
229    }
230
231    @Override
232    public String toString() {
233        return "[conversation id=" + id + ", subject =" + subject + "]";
234    }
235
236    public static final Creator<Conversation> CREATOR = new Creator<Conversation>() {
237
238        @Override
239        public Conversation createFromParcel(Parcel source) {
240            return new Conversation(source);
241        }
242
243        @Override
244        public Conversation[] newArray(int size) {
245            return new Conversation[size];
246        }
247
248    };
249
250    public static final Uri MOVE_CONVERSATIONS_URI = Uri.parse("content://moveconversations");
251
252    /**
253     * The column that needs to be updated to change the read state of a
254     * conversation.
255     */
256    public static final String UPDATE_FOLDER_COLUMN = ConversationColumns.RAW_FOLDERS;
257
258    public Conversation(Cursor cursor) {
259        if (cursor != null) {
260            id = cursor.getLong(UIProvider.CONVERSATION_ID_COLUMN);
261            uri = Uri.parse(cursor.getString(UIProvider.CONVERSATION_URI_COLUMN));
262            dateMs = cursor.getLong(UIProvider.CONVERSATION_DATE_RECEIVED_MS_COLUMN);
263            subject = cursor.getString(UIProvider.CONVERSATION_SUBJECT_COLUMN);
264            // Don't allow null subject
265            if (subject == null) {
266                subject = "";
267            }
268            hasAttachments = cursor.getInt(UIProvider.CONVERSATION_HAS_ATTACHMENTS_COLUMN) != 0;
269            String messageList = cursor.getString(UIProvider.CONVERSATION_MESSAGE_LIST_URI_COLUMN);
270            messageListUri = !TextUtils.isEmpty(messageList) ? Uri.parse(messageList) : null;
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            rawFolders = cursor.getString(UIProvider.CONVERSATION_RAW_FOLDERS_COLUMN);
276            convFlags = cursor.getInt(UIProvider.CONVERSATION_FLAGS_COLUMN);
277            personalLevel = cursor.getInt(UIProvider.CONVERSATION_PERSONAL_LEVEL_COLUMN);
278            spam = cursor.getInt(UIProvider.CONVERSATION_IS_SPAM_COLUMN) != 0;
279            phishing = cursor.getInt(UIProvider.CONVERSATION_IS_PHISHING_COLUMN) != 0;
280            muted = cursor.getInt(UIProvider.CONVERSATION_MUTED_COLUMN) != 0;
281            color = cursor.getInt(UIProvider.CONVERSATION_COLOR_COLUMN);
282            String account = cursor.getString(UIProvider.CONVERSATION_ACCOUNT_URI_COLUMN);
283            accountUri = !TextUtils.isEmpty(account) ? Uri.parse(account) : null;
284            position = NO_POSITION;
285            localDeleteOnUpdate = false;
286            conversationInfo = ConversationInfo.fromString(cursor
287                    .getString(UIProvider.CONVERSATION_INFO_COLUMN));
288            final String conversationBase =
289                    cursor.getString(UIProvider.CONVERSATION_BASE_URI_COLUMN);
290            conversationBaseUri = !TextUtils.isEmpty(conversationBase) ?
291                    Uri.parse(conversationBase) : null;
292            conversationCookie = cursor.getString(UIProvider.CONVERSATION_COOKIE_COLUMN);
293            if (conversationInfo == null) {
294                snippet = cursor.getString(UIProvider.CONVERSATION_SNIPPET_COLUMN);
295                senders = emptyIfNull(cursor.getString(UIProvider.CONVERSATION_SENDER_INFO_COLUMN));
296                numMessages = cursor.getInt(UIProvider.CONVERSATION_NUM_MESSAGES_COLUMN);
297                numDrafts = cursor.getInt(UIProvider.CONVERSATION_NUM_DRAFTS_COLUMN);
298            }
299            isRemote = cursor.getInt(UIProvider.CONVERSATION_REMOTE_COLUMN) != 0;
300        }
301    }
302
303    public Conversation() {
304    }
305
306    public static Conversation create(long id, Uri uri, String subject, long dateMs,
307            String snippet, boolean hasAttachment, Uri messageListUri, String senders,
308            int numMessages, int numDrafts, int sendingState, int priority, boolean read,
309            boolean starred, String rawFolders, int convFlags, int personalLevel, boolean spam,
310            boolean phishing, boolean muted, Uri accountUri, ConversationInfo conversationInfo,
311            Uri conversationBase, String conversationCookie, boolean isRemote) {
312
313        final Conversation conversation = new Conversation();
314
315        conversation.id = id;
316        conversation.uri = uri;
317        conversation.subject = subject;
318        conversation.dateMs = dateMs;
319        conversation.snippet = snippet;
320        conversation.hasAttachments = hasAttachment;
321        conversation.messageListUri = messageListUri;
322        conversation.senders = emptyIfNull(senders);
323        conversation.numMessages = numMessages;
324        conversation.numDrafts = numDrafts;
325        conversation.sendingState = sendingState;
326        conversation.priority = priority;
327        conversation.read = read;
328        conversation.starred = starred;
329        conversation.rawFolders = rawFolders;
330        conversation.convFlags = convFlags;
331        conversation.personalLevel = personalLevel;
332        conversation.spam = spam;
333        conversation.phishing = phishing;
334        conversation.muted = muted;
335        conversation.color = 0;
336        conversation.accountUri = accountUri;
337        conversation.conversationInfo = conversationInfo;
338        conversation.conversationBaseUri = conversationBase;
339        conversation.conversationCookie = conversationCookie;
340        conversation.isRemote = isRemote;
341        return conversation;
342    }
343
344    public ArrayList<Folder> getRawFolders() {
345        if (cachedRawFolders == null) {
346            // Create cached folders.
347            if (!TextUtils.isEmpty(rawFolders)) {
348                cachedRawFolders = Folder.getFoldersArray(rawFolders);
349            } else {
350                return new ArrayList<Folder>();
351            }
352        }
353        return cachedRawFolders;
354    }
355
356    public void setRawFolders(String raw) {
357        clearCachedFolders();
358        rawFolders = raw;
359    }
360
361    public String getRawFoldersString() {
362        return rawFolders;
363    }
364
365    private void clearCachedFolders() {
366        cachedRawFolders = null;
367        cachedDisplayableFolders = null;
368    }
369
370    public ArrayList<Folder> getRawFoldersForDisplay(Folder ignoreFolder) {
371        ArrayList<Folder> folders = getRawFolders();
372        if (cachedDisplayableFolders == null) {
373            cachedDisplayableFolders = new ArrayList<Folder>();
374            for (Folder folder : folders) {
375                // skip the ignoreFolder
376                if (ignoreFolder != null && ignoreFolder.equals(folder)) {
377                    continue;
378                }
379                cachedDisplayableFolders.add(folder);
380            }
381        }
382        return cachedDisplayableFolders;
383    }
384
385    @Override
386    public boolean equals(Object o) {
387        if (o instanceof Conversation) {
388            Conversation conv = (Conversation) o;
389            return conv.uri.equals(uri);
390        }
391        return false;
392    }
393
394    @Override
395    public int hashCode() {
396        return uri.hashCode();
397    }
398
399    /**
400     * Get if this conversation is marked as high priority.
401     */
402    public boolean isImportant() {
403        return priority == UIProvider.ConversationPriority.IMPORTANT;
404    }
405
406    /**
407     * Get if this conversation is mostly dead
408     */
409    public boolean isMostlyDead() {
410        return (convFlags & FLAG_MOSTLY_DEAD) != 0;
411    }
412
413    /**
414     * Returns true if the URI of the conversation specified as the needle was
415     * found in the collection of conversations specified as the haystack. False
416     * otherwise. This method is safe to call with null arguments.
417     *
418     * @param haystack
419     * @param needle
420     * @return true if the needle was found in the haystack, false otherwise.
421     */
422    public final static boolean contains(Collection<Conversation> haystack, Conversation needle) {
423        // If the haystack is empty, it cannot contain anything.
424        if (haystack == null || haystack.size() <= 0) {
425            return false;
426        }
427        // The null folder exists everywhere.
428        if (needle == null) {
429            return true;
430        }
431        final long toFind = needle.id;
432        for (final Conversation c : haystack) {
433            if (toFind == c.id) {
434                return true;
435            }
436        }
437        return false;
438    }
439
440    /**
441     * Returns a collection of a single conversation. This method always returns
442     * a valid collection even if the input conversation is null.
443     *
444     * @param in a conversation, possibly null.
445     * @return a collection of the conversation.
446     */
447    public static Collection<Conversation> listOf(Conversation in) {
448        final Collection<Conversation> target = (in == null) ? EMPTY : ImmutableList.of(in);
449        return target;
450    }
451
452    /**
453     * Get the snippet for this conversation. Masks that it may come from
454     * conversation info or the original deprecated snippet string.
455     */
456    public String getSnippet() {
457        return conversationInfo != null && !TextUtils.isEmpty(conversationInfo.firstSnippet) ?
458                conversationInfo.firstSnippet : snippet;
459    }
460
461    /**
462     * Get the number of messages for this conversation.
463     */
464    public int getNumMessages() {
465        return conversationInfo != null ? conversationInfo.messageCount : numMessages;
466    }
467
468    /**
469     * Get the number of drafts for this conversation.
470     */
471    public int numDrafts() {
472        return conversationInfo != null ? conversationInfo.draftCount : numDrafts;
473    }
474
475    /**
476     * Create a human-readable string of all the conversations
477     * @param collection Any collection of conversations
478     * @return string with a human readable representation of the conversations.
479     */
480    public static String toString(Collection<Conversation> collection) {
481        final StringBuilder out = new StringBuilder(collection.size() + " conversations:");
482        int count = 0;
483        for (final Conversation c : collection) {
484            count++;
485            // Indent the conversations to make them easy to read in debug
486            // output.
487            out.append("      " + count + ": " + c.toString() + "\n");
488        }
489        return out.toString();
490    }
491
492    /**
493     * Returns an empty string if the specified string is null
494     */
495    private static String emptyIfNull(String in) {
496        return in != null ? in : EMPTY_STRING;
497    }
498}
499