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