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