Conversation.java revision 6c2663dc9cade8aff34c462d244e3d72c475c664
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.android.mail.utils.LogTag;
28import com.android.mail.utils.LogUtils;
29import com.google.common.collect.ImmutableList;
30
31import org.json.JSONException;
32
33import java.util.Collection;
34import java.util.Collections;
35
36public class Conversation implements Parcelable {
37    public static final int NO_POSITION = -1;
38
39    /**
40     * @see BaseColumns#_ID
41     */
42    public long id;
43    /**
44     * @see UIProvider.ConversationColumns#URI
45     */
46    public Uri uri;
47    /**
48     * @see UIProvider.ConversationColumns#SUBJECT
49     */
50    public String subject;
51    /**
52     * @see UIProvider.ConversationColumns#DATE_RECEIVED_MS
53     */
54    public long dateMs;
55    /**
56     * @see UIProvider.ConversationColumns#SNIPPET
57     */
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    public int numMessages;
76    /**
77     * @see UIProvider.ConversationColumns#NUM_DRAFTS
78     */
79    public 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#FOLDER_LIST
98     */
99    public String folderList;
100    /**
101     * @see UIProvider.ConversationColumns#RAW_FOLDERS
102     */
103    public String rawFolders;
104    /**
105     * @see UIProvider.ConversationColumns#FLAGS
106     */
107    public int convFlags;
108    /**
109     * @see UIProvider.ConversationColumns#PERSONAL_LEVEL
110     */
111    public int personalLevel;
112    /**
113     * @see UIProvider.ConversationColumns#SPAM
114     */
115    public boolean spam;
116    /**
117     * @see UIProvider.ConversationColumns#MUTED
118     */
119    public boolean muted;
120    /**
121     * @see UIProvider.ConversationColumns#PHISHING
122     */
123    public boolean phishing;
124    /**
125     * @see UIProvider.ConversationColumns#COLOR
126     */
127    public int color;
128    /**
129     * @see UIProvider.ConversationColumns#ACCOUNT_URI
130     */
131    public Uri accountUri;
132    /**
133     * @see UIProvider.ConversationColumns#CONVERSATION_INFO
134     */
135    public ConversationInfo conversationInfo;
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    // Constituents of convFlags below
146    // Flag indicating that the item has been deleted, but will continue being
147    // shown in the list Delete/Archive of a mostly-dead item will NOT propagate
148    // the delete/archive, but WILL remove the item from the cursor
149    public static final int FLAG_MOSTLY_DEAD = 1 << 0;
150
151    /** An immutable, empty conversation list */
152    public static final Collection<Conversation> EMPTY = Collections.emptyList();
153
154    @Override
155    public int describeContents() {
156        return 0;
157    }
158
159    @Override
160    public void writeToParcel(Parcel dest, int flags) {
161        dest.writeLong(id);
162        dest.writeParcelable(uri, flags);
163        dest.writeString(subject);
164        dest.writeLong(dateMs);
165        dest.writeString(snippet);
166        dest.writeByte(hasAttachments ? (byte) 1 : 0);
167        dest.writeParcelable(messageListUri, 0);
168        dest.writeString(senders);
169        dest.writeInt(numMessages);
170        dest.writeInt(numDrafts);
171        dest.writeInt(sendingState);
172        dest.writeInt(priority);
173        dest.writeByte(read ? (byte) 1 : 0);
174        dest.writeByte(starred ? (byte) 1 : 0);
175        dest.writeString(folderList);
176        dest.writeString(rawFolders);
177        dest.writeInt(convFlags);
178        dest.writeInt(personalLevel);
179        dest.writeInt(spam ? 1 : 0);
180        dest.writeInt(phishing ? 1 : 0);
181        dest.writeInt(muted ? 1 : 0);
182        dest.writeInt(color);
183        dest.writeParcelable(accountUri, 0);
184    }
185
186    private Conversation(Parcel in) {
187        id = in.readLong();
188        uri = in.readParcelable(null);
189        subject = in.readString();
190        dateMs = in.readLong();
191        snippet = in.readString();
192        hasAttachments = (in.readByte() != 0);
193        messageListUri = in.readParcelable(null);
194        senders = in.readString();
195        numMessages = in.readInt();
196        numDrafts = in.readInt();
197        sendingState = in.readInt();
198        priority = in.readInt();
199        read = (in.readByte() != 0);
200        starred = (in.readByte() != 0);
201        folderList = in.readString();
202        rawFolders = in.readString();
203        convFlags = in.readInt();
204        personalLevel = in.readInt();
205        spam = in.readInt() != 0;
206        phishing = in.readInt() != 0;
207        muted = in.readInt() != 0;
208        color = in.readInt();
209        accountUri = in.readParcelable(null);
210        position = NO_POSITION;
211        localDeleteOnUpdate = false;
212    }
213
214    @Override
215    public String toString() {
216        return "[conversation id=" + id + ", subject =" + subject + "]";
217    }
218
219    public static final Creator<Conversation> CREATOR = new Creator<Conversation>() {
220
221        @Override
222        public Conversation createFromParcel(Parcel source) {
223            return new Conversation(source);
224        }
225
226        @Override
227        public Conversation[] newArray(int size) {
228            return new Conversation[size];
229        }
230
231    };
232
233    public static final Uri MOVE_CONVERSATIONS_URI = Uri.parse("content://moveconversations");
234
235    /**
236     * The columns that need to be updated to change the read state of a conversation.
237     */
238    public static final String[] UPDATE_FOLDER_COLUMNS = new String[] {
239            ConversationColumns.FOLDER_LIST, ConversationColumns.RAW_FOLDERS
240    };
241
242    private static final String LOG_TAG = LogTag.getLogTag();
243
244    public Conversation(Cursor cursor) {
245        if (cursor != null) {
246            id = cursor.getLong(UIProvider.CONVERSATION_ID_COLUMN);
247            uri = Uri.parse(cursor.getString(UIProvider.CONVERSATION_URI_COLUMN));
248            dateMs = cursor.getLong(UIProvider.CONVERSATION_DATE_RECEIVED_MS_COLUMN);
249            subject = cursor.getString(UIProvider.CONVERSATION_SUBJECT_COLUMN);
250            // Don't allow null subject
251            if (subject == null) {
252                subject = "";
253            }
254            snippet = cursor.getString(UIProvider.CONVERSATION_SNIPPET_COLUMN);
255            hasAttachments = cursor.getInt(UIProvider.CONVERSATION_HAS_ATTACHMENTS_COLUMN) != 0;
256            String messageList = cursor.getString(UIProvider.CONVERSATION_MESSAGE_LIST_URI_COLUMN);
257            messageListUri = !TextUtils.isEmpty(messageList) ? Uri.parse(messageList) : null;
258            numMessages = cursor.getInt(UIProvider.CONVERSATION_NUM_MESSAGES_COLUMN);
259            numDrafts = cursor.getInt(UIProvider.CONVERSATION_NUM_DRAFTS_COLUMN);
260            sendingState = cursor.getInt(UIProvider.CONVERSATION_SENDING_STATE_COLUMN);
261            priority = cursor.getInt(UIProvider.CONVERSATION_PRIORITY_COLUMN);
262            read = cursor.getInt(UIProvider.CONVERSATION_READ_COLUMN) != 0;
263            starred = cursor.getInt(UIProvider.CONVERSATION_STARRED_COLUMN) != 0;
264            folderList = cursor.getString(UIProvider.CONVERSATION_FOLDER_LIST_COLUMN);
265            rawFolders = cursor.getString(UIProvider.CONVERSATION_RAW_FOLDERS_COLUMN);
266            convFlags = cursor.getInt(UIProvider.CONVERSATION_FLAGS_COLUMN);
267            personalLevel = cursor.getInt(UIProvider.CONVERSATION_PERSONAL_LEVEL_COLUMN);
268            spam = cursor.getInt(UIProvider.CONVERSATION_IS_SPAM_COLUMN) != 0;
269            phishing = cursor.getInt(UIProvider.CONVERSATION_IS_PHISHING_COLUMN) != 0;
270            muted = cursor.getInt(UIProvider.CONVERSATION_MUTED_COLUMN) != 0;
271            color = cursor.getInt(UIProvider.CONVERSATION_COLOR_COLUMN);
272            String account = cursor.getString(UIProvider.CONVERSATION_ACCOUNT_URI_COLUMN);
273            accountUri = !TextUtils.isEmpty(account) ? Uri.parse(account) : null;
274            position = NO_POSITION;
275            localDeleteOnUpdate = false;
276            senders = cursor.getString(UIProvider.CONVERSATION_SENDER_INFO_COLUMN);
277            try {
278                conversationInfo = ConversationInfo.fromString(cursor
279                        .getString(UIProvider.CONVERSATION_INFO_COLUMN));
280            } catch (JSONException e) {
281                LogUtils.w(LOG_TAG, e,
282                        "Unable to instantiate ConversationInfo. Try to continue anyway");
283            }
284        }
285    }
286
287    public Conversation() {
288    }
289
290    public static Conversation create(long id, Uri uri, String subject, long dateMs,
291            String snippet, boolean hasAttachment, Uri messageListUri, String senders,
292            int numMessages, int numDrafts, int sendingState, int priority, boolean read,
293            boolean starred, String folderList, String rawFolders, int convFlags,
294            int personalLevel, boolean spam, boolean phishing, boolean muted, Uri accountUri) {
295
296        final Conversation conversation = new Conversation();
297
298        conversation.id = id;
299        conversation.uri = uri;
300        conversation.subject = subject;
301        conversation.dateMs = dateMs;
302        conversation.snippet = snippet;
303        conversation.hasAttachments = hasAttachment;
304        conversation.messageListUri = messageListUri;
305        conversation.senders = senders;
306        conversation.numMessages = numMessages;
307        conversation.numDrafts = numDrafts;
308        conversation.sendingState = sendingState;
309        conversation.priority = priority;
310        conversation.read = read;
311        conversation.starred = starred;
312        conversation.folderList = folderList;
313        conversation.rawFolders = rawFolders;
314        conversation.convFlags = convFlags;
315        conversation.personalLevel = personalLevel;
316        conversation.spam = spam;
317        conversation.phishing = phishing;
318        conversation.muted = muted;
319        conversation.color = 0;
320        conversation.accountUri = accountUri;
321        return conversation;
322    }
323
324    @Override
325    public boolean equals(Object o) {
326        if (o instanceof Conversation) {
327            Conversation conv = (Conversation) o;
328            return conv.uri.equals(uri);
329        }
330        return false;
331    }
332
333    @Override
334    public int hashCode() {
335        return uri.hashCode();
336    }
337
338    /**
339     * Get if this conversation is marked as high priority.
340     */
341    public boolean isImportant() {
342        return priority == UIProvider.ConversationPriority.IMPORTANT;
343    }
344
345    /**
346     * Get if this conversation is mostly dead
347     */
348    public boolean isMostlyDead() {
349        return (convFlags & FLAG_MOSTLY_DEAD) != 0;
350    }
351
352    /**
353     * Returns true if the URI of the conversation specified as the needle was
354     * found in the collection of conversations specified as the haystack. False
355     * otherwise. This method is safe to call with nullarguments.
356     *
357     * @param haystack
358     * @param needle
359     * @return true if the needle was found in the haystack, false otherwise.
360     */
361    public final static boolean contains(Collection<Conversation> haystack, Conversation needle) {
362        // If the haystack is empty, it cannot contain anything.
363        if (haystack == null || haystack.size() <= 0) {
364            return false;
365        }
366        // The null folder exists everywhere.
367        if (needle == null) {
368            return true;
369        }
370        final long toFind = needle.id;
371        for (final Conversation c : haystack) {
372            if (toFind == c.id) {
373                return true;
374            }
375        }
376        return false;
377    }
378
379    /**
380     * Returns a collection of a single conversation. This method always returns
381     * a valid collection even if the input conversation is null.
382     *
383     * @param in a conversation, possibly null.
384     * @return a collection of the conversation.
385     */
386    public static Collection<Conversation> listOf(Conversation in) {
387        final Collection<Conversation> target = (in == null) ? EMPTY : ImmutableList.of(in);
388        return target;
389    }
390
391    /**
392     * Create a human-readable string of all the conversations
393     *
394     * @param collection Any collection of conversations
395     * @return string with a human readable representation of the conversations.
396     */
397    public static String toString(Collection<Conversation> collection) {
398        final StringBuilder out = new StringBuilder(collection.size() + " conversations:");
399        int count = 0;
400        for (final Conversation c : collection) {
401            count++;
402            // Indent the conversations to make them easy to read in debug
403            // output.
404            out.append("      " + count + ": " + c.toString() + "\n");
405        }
406        return out.toString();
407    }
408}
409