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