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