Conversation.java revision 648df3f0b0ebcd3c4adf907d70ff0938e5dfc78f
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 folders for a
237     * conversation.
238     */
239    public static final String[] UPDATE_FOLDER_COLUMNS = new String[] {
240            ConversationColumns.FOLDER_LIST, ConversationColumns.RAW_FOLDERS
241    };
242
243    private static final String LOG_TAG = LogTag.getLogTag();
244
245    public Conversation(Cursor cursor) {
246        if (cursor != null) {
247            id = cursor.getLong(UIProvider.CONVERSATION_ID_COLUMN);
248            uri = Uri.parse(cursor.getString(UIProvider.CONVERSATION_URI_COLUMN));
249            dateMs = cursor.getLong(UIProvider.CONVERSATION_DATE_RECEIVED_MS_COLUMN);
250            subject = cursor.getString(UIProvider.CONVERSATION_SUBJECT_COLUMN);
251            // Don't allow null subject
252            if (subject == null) {
253                subject = "";
254            }
255            snippet = cursor.getString(UIProvider.CONVERSATION_SNIPPET_COLUMN);
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            numMessages = cursor.getInt(UIProvider.CONVERSATION_NUM_MESSAGES_COLUMN);
260            numDrafts = cursor.getInt(UIProvider.CONVERSATION_NUM_DRAFTS_COLUMN);
261            sendingState = cursor.getInt(UIProvider.CONVERSATION_SENDING_STATE_COLUMN);
262            priority = cursor.getInt(UIProvider.CONVERSATION_PRIORITY_COLUMN);
263            read = cursor.getInt(UIProvider.CONVERSATION_READ_COLUMN) != 0;
264            starred = cursor.getInt(UIProvider.CONVERSATION_STARRED_COLUMN) != 0;
265            folderList = cursor.getString(UIProvider.CONVERSATION_FOLDER_LIST_COLUMN);
266            rawFolders = cursor.getString(UIProvider.CONVERSATION_RAW_FOLDERS_COLUMN);
267            convFlags = cursor.getInt(UIProvider.CONVERSATION_FLAGS_COLUMN);
268            personalLevel = cursor.getInt(UIProvider.CONVERSATION_PERSONAL_LEVEL_COLUMN);
269            spam = cursor.getInt(UIProvider.CONVERSATION_IS_SPAM_COLUMN) != 0;
270            phishing = cursor.getInt(UIProvider.CONVERSATION_IS_PHISHING_COLUMN) != 0;
271            muted = cursor.getInt(UIProvider.CONVERSATION_MUTED_COLUMN) != 0;
272            color = cursor.getInt(UIProvider.CONVERSATION_COLOR_COLUMN);
273            String account = cursor.getString(UIProvider.CONVERSATION_ACCOUNT_URI_COLUMN);
274            accountUri = !TextUtils.isEmpty(account) ? Uri.parse(account) : null;
275            position = NO_POSITION;
276            localDeleteOnUpdate = false;
277            senders = cursor.getString(UIProvider.CONVERSATION_SENDER_INFO_COLUMN);
278            try {
279                conversationInfo = ConversationInfo.fromString(cursor
280                        .getString(UIProvider.CONVERSATION_INFO_COLUMN));
281            } catch (JSONException e) {
282                LogUtils.w(LOG_TAG, e,
283                        "Unable to instantiate ConversationInfo. Try to continue anyway");
284            }
285        }
286    }
287
288    public Conversation() {
289    }
290
291    public static Conversation create(long id, Uri uri, String subject, long dateMs,
292            String snippet, boolean hasAttachment, Uri messageListUri, String senders,
293            int numMessages, int numDrafts, int sendingState, int priority, boolean read,
294            boolean starred, String folderList, String rawFolders, int convFlags,
295            int personalLevel, boolean spam, boolean phishing, boolean muted, Uri accountUri) {
296
297        final Conversation conversation = new Conversation();
298
299        conversation.id = id;
300        conversation.uri = uri;
301        conversation.subject = subject;
302        conversation.dateMs = dateMs;
303        conversation.snippet = snippet;
304        conversation.hasAttachments = hasAttachment;
305        conversation.messageListUri = messageListUri;
306        conversation.senders = senders;
307        conversation.numMessages = numMessages;
308        conversation.numDrafts = numDrafts;
309        conversation.sendingState = sendingState;
310        conversation.priority = priority;
311        conversation.read = read;
312        conversation.starred = starred;
313        conversation.folderList = folderList;
314        conversation.rawFolders = rawFolders;
315        conversation.convFlags = convFlags;
316        conversation.personalLevel = personalLevel;
317        conversation.spam = spam;
318        conversation.phishing = phishing;
319        conversation.muted = muted;
320        conversation.color = 0;
321        conversation.accountUri = accountUri;
322        return conversation;
323    }
324
325    @Override
326    public boolean equals(Object o) {
327        if (o instanceof Conversation) {
328            Conversation conv = (Conversation) o;
329            return conv.uri.equals(uri);
330        }
331        return false;
332    }
333
334    @Override
335    public int hashCode() {
336        return uri.hashCode();
337    }
338
339    /**
340     * Get if this conversation is marked as high priority.
341     */
342    public boolean isImportant() {
343        return priority == UIProvider.ConversationPriority.IMPORTANT;
344    }
345
346    /**
347     * Get if this conversation is mostly dead
348     */
349    public boolean isMostlyDead() {
350        return (convFlags & FLAG_MOSTLY_DEAD) != 0;
351    }
352
353    /**
354     * Returns true if the URI of the conversation specified as the needle was
355     * found in the collection of conversations specified as the haystack. False
356     * otherwise. This method is safe to call with nullarguments.
357     *
358     * @param haystack
359     * @param needle
360     * @return true if the needle was found in the haystack, false otherwise.
361     */
362    public final static boolean contains(Collection<Conversation> haystack, Conversation needle) {
363        // If the haystack is empty, it cannot contain anything.
364        if (haystack == null || haystack.size() <= 0) {
365            return false;
366        }
367        // The null folder exists everywhere.
368        if (needle == null) {
369            return true;
370        }
371        final long toFind = needle.id;
372        for (final Conversation c : haystack) {
373            if (toFind == c.id) {
374                return true;
375            }
376        }
377        return false;
378    }
379
380    /**
381     * Returns a collection of a single conversation. This method always returns
382     * a valid collection even if the input conversation is null.
383     *
384     * @param in a conversation, possibly null.
385     * @return a collection of the conversation.
386     */
387    public static Collection<Conversation> listOf(Conversation in) {
388        final Collection<Conversation> target = (in == null) ? EMPTY : ImmutableList.of(in);
389        return target;
390    }
391
392    /**
393     * Create a human-readable string of all the conversations
394     *
395     * @param collection Any collection of conversations
396     * @return string with a human readable representation of the conversations.
397     */
398    public static String toString(Collection<Conversation> collection) {
399        final StringBuilder out = new StringBuilder(collection.size() + " conversations:");
400        int count = 0;
401        for (final Conversation c : collection) {
402            count++;
403            // Indent the conversations to make them easy to read in debug
404            // output.
405            out.append("      " + count + ": " + c.toString() + "\n");
406        }
407        return out.toString();
408    }
409}
410