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