Conversation.java revision 863e44160d9175023d30e7e225ecb69ad3892eec
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.ContentProviderClient;
20import android.content.ContentValues;
21import android.content.Context;
22import android.database.Cursor;
23import android.net.Uri;
24import android.os.Parcel;
25import android.os.Parcelable;
26import android.text.TextUtils;
27
28import com.android.mail.browse.ConversationCursor.ConversationOperation;
29import com.android.mail.browse.ConversationCursor.ConversationProvider;
30import com.android.mail.providers.UIProvider.ConversationColumns;
31import com.google.common.collect.Lists;
32
33import java.util.ArrayList;
34import java.util.Arrays;
35import java.util.Collection;
36
37public class Conversation implements Parcelable {
38    public static final int NO_POSITION = -1;
39
40    public long id;
41    public Uri uri;
42    public String subject;
43    public long dateMs;
44    public String snippet;
45    public boolean hasAttachments;
46    public Uri messageListUri;
47    public String senders;
48    public int numMessages;
49    public int numDrafts;
50    public int sendingState;
51    public int priority;
52    public boolean read;
53    public boolean starred;
54    public String folderList;
55    public String rawFolders;
56    public int convFlags;
57    public int personalLevel;
58    public boolean spam;
59    public boolean muted;
60
61    // Used within the UI to indicate the adapter position of this conversation
62    public transient int position;
63    // Used within the UI to indicate that a Conversation should be removed from the
64    // ConversationCursor when executing an update, e.g. the the Conversation is no longer
65    // in the ConversationList for the current folder, that is it's now in some other folder(s)
66    public transient boolean localDeleteOnUpdate;
67
68    @Override
69    public int describeContents() {
70        return 0;
71    }
72
73    @Override
74    public void writeToParcel(Parcel dest, int flags) {
75        dest.writeLong(id);
76        dest.writeParcelable(uri, flags);
77        dest.writeString(subject);
78        dest.writeLong(dateMs);
79        dest.writeString(snippet);
80        dest.writeByte(hasAttachments ? (byte) 1 : 0);
81        dest.writeParcelable(messageListUri, 0);
82        dest.writeString(senders);
83        dest.writeInt(numMessages);
84        dest.writeInt(numDrafts);
85        dest.writeInt(sendingState);
86        dest.writeInt(priority);
87        dest.writeByte(read ? (byte) 1 : 0);
88        dest.writeByte(starred ? (byte) 1 : 0);
89        dest.writeString(folderList);
90        dest.writeString(rawFolders);
91        dest.writeInt(convFlags);
92        dest.writeInt(personalLevel);
93        dest.writeInt(spam ? 1 : 0);
94        dest.writeInt(muted ? 1 : 0);
95    }
96
97    private Conversation(Parcel in) {
98        id = in.readLong();
99        uri = in.readParcelable(null);
100        subject = in.readString();
101        dateMs = in.readLong();
102        snippet = in.readString();
103        hasAttachments = (in.readByte() != 0);
104        messageListUri = in.readParcelable(null);
105        senders = in.readString();
106        numMessages = in.readInt();
107        numDrafts = in.readInt();
108        sendingState = in.readInt();
109        priority = in.readInt();
110        read = (in.readByte() != 0);
111        starred = (in.readByte() != 0);
112        folderList = in.readString();
113        rawFolders = in.readString();
114        convFlags = in.readInt();
115        personalLevel = in.readInt();
116        spam = in.readInt() != 0;
117        muted = in.readInt() != 0;
118        position = NO_POSITION;
119        localDeleteOnUpdate = false;
120    }
121
122    @Override
123    public String toString() {
124        return "[conversation id=" + id + "]";
125    }
126
127    public static final Creator<Conversation> CREATOR = new Creator<Conversation>() {
128
129        @Override
130        public Conversation createFromParcel(Parcel source) {
131            return new Conversation(source);
132        }
133
134        @Override
135        public Conversation[] newArray(int size) {
136            return new Conversation[size];
137        }
138
139    };
140
141    public Conversation(Cursor cursor) {
142        if (cursor != null) {
143            id = cursor.getLong(UIProvider.CONVERSATION_ID_COLUMN);
144            uri = Uri.parse(cursor.getString(UIProvider.CONVERSATION_URI_COLUMN));
145            dateMs = cursor.getLong(UIProvider.CONVERSATION_DATE_RECEIVED_MS_COLUMN);
146            subject = cursor.getString(UIProvider.CONVERSATION_SUBJECT_COLUMN);
147            // Don't allow null subject
148            if (subject == null) {
149                subject = "";
150            }
151            snippet = cursor.getString(UIProvider.CONVERSATION_SNIPPET_COLUMN);
152            hasAttachments = cursor.getInt(UIProvider.CONVERSATION_HAS_ATTACHMENTS_COLUMN) != 0;
153            String messageList = cursor
154                    .getString(UIProvider.CONVERSATION_MESSAGE_LIST_URI_COLUMN);
155            messageListUri = !TextUtils.isEmpty(messageList) ? Uri.parse(messageList) : null;
156            senders = cursor.getString(UIProvider.CONVERSATION_SENDER_INFO_COLUMN);
157            numMessages = cursor.getInt(UIProvider.CONVERSATION_NUM_MESSAGES_COLUMN);
158            numDrafts = cursor.getInt(UIProvider.CONVERSATION_NUM_DRAFTS_COLUMN);
159            sendingState = cursor.getInt(UIProvider.CONVERSATION_SENDING_STATE_COLUMN);
160            priority = cursor.getInt(UIProvider.CONVERSATION_PRIORITY_COLUMN);
161            read = cursor.getInt(UIProvider.CONVERSATION_READ_COLUMN) != 0;
162            starred = cursor.getInt(UIProvider.CONVERSATION_STARRED_COLUMN) != 0;
163            folderList = cursor.getString(UIProvider.CONVERSATION_FOLDER_LIST_COLUMN);
164            rawFolders = cursor.getString(UIProvider.CONVERSATION_RAW_FOLDERS_COLUMN);
165            convFlags = cursor.getInt(UIProvider.CONVERSATION_FLAGS_COLUMN);
166            personalLevel = cursor.getInt(UIProvider.CONVERSATION_PERSONAL_LEVEL_COLUMN);
167            spam = cursor.getInt(UIProvider.CONVERSATION_IS_SPAM_COLUMN) != 0;
168            muted = cursor.getInt(UIProvider.CONVERSATION_MUTED_COLUMN) != 0;
169            position = NO_POSITION;
170            localDeleteOnUpdate = false;
171        }
172    }
173
174    private Conversation() {
175    }
176
177    // TODO: (mindyp) remove once gmail is updated and checked in.
178    @Deprecated
179    public static Conversation create(long id, Uri uri, String subject, long dateMs,
180            String snippet, boolean hasAttachment, Uri messageListUri, String senders,
181            int numMessages, int numDrafts, int sendingState, int priority, boolean read,
182            boolean starred, String folderList, String rawFolders, int convFlags,
183            int personalLevel) {
184        return Conversation.create(id, uri, subject, dateMs, snippet, hasAttachment,
185                messageListUri, senders, numMessages, numDrafts, sendingState, priority, read,
186                starred, folderList, rawFolders, convFlags, personalLevel, false, false);
187    }
188
189    public static Conversation create(long id, Uri uri, String subject, long dateMs,
190            String snippet, boolean hasAttachment, Uri messageListUri, String senders,
191            int numMessages, int numDrafts, int sendingState, int priority, boolean read,
192            boolean starred, String folderList, String rawFolders, int convFlags,
193            int personalLevel, boolean spam, boolean muted) {
194
195        final Conversation conversation = new Conversation();
196
197        conversation.id = id;
198        conversation.uri = uri;
199        conversation.subject = subject;
200        conversation.dateMs = dateMs;
201        conversation.snippet = snippet;
202        conversation.hasAttachments = hasAttachment;
203        conversation.messageListUri = messageListUri;
204        conversation.senders = senders;
205        conversation.numMessages = numMessages;
206        conversation.numDrafts = numDrafts;
207        conversation.sendingState = sendingState;
208        conversation.priority = priority;
209        conversation.read = read;
210        conversation.starred = starred;
211        conversation.folderList = folderList;
212        conversation.rawFolders = rawFolders;
213        conversation.convFlags = convFlags;
214        conversation.personalLevel = personalLevel;
215        conversation.spam = spam;
216        conversation.muted = muted;
217        return conversation;
218    }
219
220    /**
221     * Get if this conversation is marked as high priority.
222     */
223    public boolean isImportant() {
224        return priority == UIProvider.ConversationPriority.IMPORTANT;
225    }
226
227    // Below are methods that update Conversation data (update/delete)
228
229    /**
230     * Update an integer column for a single conversation (see updateBoolean below)
231     */
232    public int updateInt(Context context, String columnName, int value) {
233        return updateInt(context, Arrays.asList(this), columnName, value);
234    }
235
236    /**
237     * Update an integer column for a group of conversations (see updateValues below)
238     */
239    public static int updateInt(Context context, Collection<Conversation> conversations,
240            String columnName, int value) {
241        ContentValues cv = new ContentValues();
242        cv.put(columnName, value);
243        return updateValues(context, conversations, cv);
244    }
245
246    /**
247     * Update a boolean column for a single conversation (see updateBoolean below)
248     */
249    public int updateBoolean(Context context, String columnName, boolean value) {
250        return updateBoolean(context, Arrays.asList(this), columnName, value);
251    }
252
253    /**
254     * Update a string column for a group of conversations (see updateValues below)
255     */
256    public static int updateBoolean(Context context, Collection<Conversation> conversations,
257            String columnName, boolean value) {
258        ContentValues cv = new ContentValues();
259        cv.put(columnName, value);
260        return updateValues(context, conversations, cv);
261    }
262
263    /**
264     * Update a string column for a single conversation (see updateString below)
265     */
266    public int updateString(Context context, String columnName, String value) {
267        return updateString(context, Arrays.asList(this), columnName, value);
268    }
269
270    /**
271     * Update a string column for a group of conversations (see updateValues below)
272     */
273    public static int updateString(Context context, Collection<Conversation> conversations,
274            String columnName, String value) {
275        ContentValues cv = new ContentValues();
276        cv.put(columnName, value);
277        return updateValues(context, conversations, cv);
278    }
279
280    /**
281     * Update a boolean column for a group of conversations, immediately in the UI and in a single
282     * transaction in the underlying provider
283     * @param conversations a collection of conversations
284     * @param context the caller's context
285     * @param columnName the column to update
286     * @param value the new value
287     * @return the sequence number of the operation (for undo)
288     */
289    private static int updateValues(Context context, Collection<Conversation> conversations,
290            ContentValues values) {
291        return apply(context,
292                getOperationsForConversations(conversations, ConversationOperation.UPDATE, values));
293    }
294
295    private static ArrayList<ConversationOperation> getOperationsForConversations(
296            Collection<Conversation> conversations, int op, ContentValues values) {
297        return getOperationsForConversations(conversations, op, values, false /* autoNotify */);
298    }
299
300    private static ArrayList<ConversationOperation> getOperationsForConversations(
301            Collection<Conversation> conversations, int type, ContentValues values,
302            boolean autoNotify) {
303        final ArrayList<ConversationOperation> ops = Lists.newArrayList();
304        for (Conversation conv: conversations) {
305            ConversationOperation op = new ConversationOperation(type, conv, values, autoNotify);
306            ops.add(op);
307        }
308        return ops;
309    }
310
311    /**
312     * Delete a single conversation
313     * @param context the caller's context
314     * @return the sequence number of the operation (for undo)
315     */
316    public int delete(Context context) {
317        ArrayList<Conversation> conversations = new ArrayList<Conversation>();
318        conversations.add(this);
319        return delete(context, conversations);
320    }
321
322    /**
323     * Mark a single conversation read/unread.
324     * @param context the caller's context
325     * @param read true for read, false for unread
326     * @return the sequence number of the operation (for undo)
327     */
328    public int markRead(Context context, boolean read) {
329        ContentValues values = new ContentValues();
330        values.put(ConversationColumns.READ, read);
331
332        return apply(
333                context,
334                getOperationsForConversations(Arrays.asList(this), ConversationOperation.UPDATE,
335                        values, true /* autoNotify */));
336    }
337
338    /**
339     * Delete a group of conversations immediately in the UI and in a single transaction in the
340     * underlying provider
341     * @param context the caller's context
342     * @param conversations a collection of conversations
343     * @return the sequence number of the operation (for undo)
344     */
345    public static int delete(Context context, Collection<Conversation> conversations) {
346        ArrayList<ConversationOperation> ops = Lists.newArrayList();
347        for (Conversation conv: conversations) {
348            ConversationOperation op =
349                    new ConversationOperation(ConversationOperation.DELETE, conv);
350            ops.add(op);
351        }
352        return apply(context, ops);
353    }
354
355    // Convenience methods
356    private static int apply(Context context, ArrayList<ConversationOperation> operations) {
357        ContentProviderClient client =
358                context.getContentResolver().acquireContentProviderClient(
359                        ConversationProvider.AUTHORITY);
360        try {
361            ConversationProvider cp = (ConversationProvider)client.getLocalContentProvider();
362            return cp.apply(operations);
363        } finally {
364            client.release();
365        }
366    }
367
368    private static void undoLocal(Context context) {
369        ContentProviderClient client =
370                context.getContentResolver().acquireContentProviderClient(
371                        ConversationProvider.AUTHORITY);
372        try {
373            ConversationProvider cp = (ConversationProvider)client.getLocalContentProvider();
374            cp.undo();
375        } finally {
376            client.release();
377        }
378    }
379
380    public static void undo(final Context context, final Uri undoUri) {
381        new Thread(new Runnable() {
382            @Override
383            public void run() {
384                Cursor c = context.getContentResolver().query(undoUri, UIProvider.UNDO_PROJECTION,
385                        null, null, null);
386                if (c != null) {
387                    c.close();
388                }
389            }
390        }).start();
391        undoLocal(context);
392    }
393
394    public static int archive(Context context, Collection<Conversation> conversations) {
395        ArrayList<ConversationOperation> ops = Lists.newArrayList();
396        for (Conversation conv: conversations) {
397            ConversationOperation op =
398                    new ConversationOperation(ConversationOperation.ARCHIVE, conv);
399            ops.add(op);
400        }
401        return apply(context, ops);
402    }
403
404    public static int mute(Context context, Collection<Conversation> conversations) {
405        ArrayList<ConversationOperation> ops = Lists.newArrayList();
406        for (Conversation conv: conversations) {
407            ConversationOperation op =
408                    new ConversationOperation(ConversationOperation.MUTE, conv);
409            ops.add(op);
410        }
411        return apply(context, ops);
412    }
413
414    public static int reportSpam(Context context, Collection<Conversation> conversations) {
415        ArrayList<ConversationOperation> ops = Lists.newArrayList();
416        for (Conversation conv: conversations) {
417            ConversationOperation op =
418                    new ConversationOperation(ConversationOperation.REPORT_SPAM, conv);
419            ops.add(op);
420        }
421        return apply(context, ops);
422    }
423}