Conversation.java revision acf6039a23382f18c35f6b487d90d53cb67b5858
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 static final Uri MOVE_CONVERSATIONS_URI = Uri.parse("content://moveconversations");
142
143    public Conversation(Cursor cursor) {
144        if (cursor != null) {
145            id = cursor.getLong(UIProvider.CONVERSATION_ID_COLUMN);
146            uri = Uri.parse(cursor.getString(UIProvider.CONVERSATION_URI_COLUMN));
147            dateMs = cursor.getLong(UIProvider.CONVERSATION_DATE_RECEIVED_MS_COLUMN);
148            subject = cursor.getString(UIProvider.CONVERSATION_SUBJECT_COLUMN);
149            // Don't allow null subject
150            if (subject == null) {
151                subject = "";
152            }
153            snippet = cursor.getString(UIProvider.CONVERSATION_SNIPPET_COLUMN);
154            hasAttachments = cursor.getInt(UIProvider.CONVERSATION_HAS_ATTACHMENTS_COLUMN) != 0;
155            String messageList = cursor
156                    .getString(UIProvider.CONVERSATION_MESSAGE_LIST_URI_COLUMN);
157            messageListUri = !TextUtils.isEmpty(messageList) ? Uri.parse(messageList) : null;
158            senders = cursor.getString(UIProvider.CONVERSATION_SENDER_INFO_COLUMN);
159            numMessages = cursor.getInt(UIProvider.CONVERSATION_NUM_MESSAGES_COLUMN);
160            numDrafts = cursor.getInt(UIProvider.CONVERSATION_NUM_DRAFTS_COLUMN);
161            sendingState = cursor.getInt(UIProvider.CONVERSATION_SENDING_STATE_COLUMN);
162            priority = cursor.getInt(UIProvider.CONVERSATION_PRIORITY_COLUMN);
163            read = cursor.getInt(UIProvider.CONVERSATION_READ_COLUMN) != 0;
164            starred = cursor.getInt(UIProvider.CONVERSATION_STARRED_COLUMN) != 0;
165            folderList = cursor.getString(UIProvider.CONVERSATION_FOLDER_LIST_COLUMN);
166            rawFolders = cursor.getString(UIProvider.CONVERSATION_RAW_FOLDERS_COLUMN);
167            convFlags = cursor.getInt(UIProvider.CONVERSATION_FLAGS_COLUMN);
168            personalLevel = cursor.getInt(UIProvider.CONVERSATION_PERSONAL_LEVEL_COLUMN);
169            spam = cursor.getInt(UIProvider.CONVERSATION_IS_SPAM_COLUMN) != 0;
170            muted = cursor.getInt(UIProvider.CONVERSATION_MUTED_COLUMN) != 0;
171            position = NO_POSITION;
172            localDeleteOnUpdate = false;
173        }
174    }
175
176    private Conversation() {
177    }
178
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, boolean spam, boolean muted) {
184
185        final Conversation conversation = new Conversation();
186
187        conversation.id = id;
188        conversation.uri = uri;
189        conversation.subject = subject;
190        conversation.dateMs = dateMs;
191        conversation.snippet = snippet;
192        conversation.hasAttachments = hasAttachment;
193        conversation.messageListUri = messageListUri;
194        conversation.senders = senders;
195        conversation.numMessages = numMessages;
196        conversation.numDrafts = numDrafts;
197        conversation.sendingState = sendingState;
198        conversation.priority = priority;
199        conversation.read = read;
200        conversation.starred = starred;
201        conversation.folderList = folderList;
202        conversation.rawFolders = rawFolders;
203        conversation.convFlags = convFlags;
204        conversation.personalLevel = personalLevel;
205        conversation.spam = spam;
206        conversation.muted = muted;
207        return conversation;
208    }
209
210    /**
211     * Get if this conversation is marked as high priority.
212     */
213    public boolean isImportant() {
214        return priority == UIProvider.ConversationPriority.IMPORTANT;
215    }
216
217    // Below are methods that update Conversation data (update/delete)
218
219    /**
220     * Update an integer column for a single conversation (see updateBoolean below)
221     */
222    public int updateInt(Context context, String columnName, int value) {
223        return updateInt(context, Arrays.asList(this), columnName, value);
224    }
225
226    /**
227     * Update an integer column for a group of conversations (see updateValues below)
228     */
229    public static int updateInt(Context context, Collection<Conversation> conversations,
230            String columnName, int value) {
231        ContentValues cv = new ContentValues();
232        cv.put(columnName, value);
233        return updateValues(context, conversations, cv);
234    }
235
236    /**
237     * Update a boolean column for a single conversation (see updateBoolean below)
238     */
239    public int updateBoolean(Context context, String columnName, boolean value) {
240        return updateBoolean(context, Arrays.asList(this), columnName, value);
241    }
242
243    /**
244     * Update a string column for a group of conversations (see updateValues below)
245     */
246    public static int updateBoolean(Context context, Collection<Conversation> conversations,
247            String columnName, boolean value) {
248        ContentValues cv = new ContentValues();
249        cv.put(columnName, value);
250        return updateValues(context, conversations, cv);
251    }
252
253    /**
254     * Update a string column for a single conversation (see updateString below)
255     */
256    public int updateString(Context context, String columnName, String value) {
257        return updateString(context, Arrays.asList(this), columnName, value);
258    }
259
260    /**
261     * Update a string column for a group of conversations (see updateValues below)
262     */
263    public static int updateString(Context context, Collection<Conversation> conversations,
264            String columnName, String value) {
265        ContentValues cv = new ContentValues();
266        cv.put(columnName, value);
267        return updateValues(context, conversations, cv);
268    }
269
270    /**
271     * Update a boolean column for a group of conversations, immediately in the UI and in a single
272     * transaction in the underlying provider
273     * @param conversations a collection of conversations
274     * @param context the caller's context
275     * @param columnName the column to update
276     * @param value the new value
277     * @return the sequence number of the operation (for undo)
278     */
279    private static int updateValues(Context context, Collection<Conversation> conversations,
280            ContentValues values) {
281        return apply(context,
282                getOperationsForConversations(conversations, ConversationOperation.UPDATE, values));
283    }
284
285    private static ArrayList<ConversationOperation> getOperationsForConversations(
286            Collection<Conversation> conversations, int op, ContentValues values) {
287        return getOperationsForConversations(conversations, op, values, false /* autoNotify */);
288    }
289
290    private static ArrayList<ConversationOperation> getOperationsForConversations(
291            Collection<Conversation> conversations, int type, ContentValues values,
292            boolean autoNotify) {
293        final ArrayList<ConversationOperation> ops = Lists.newArrayList();
294        for (Conversation conv: conversations) {
295            ConversationOperation op = new ConversationOperation(type, conv, values, autoNotify);
296            ops.add(op);
297        }
298        return ops;
299    }
300
301    /**
302     * Delete a single conversation
303     * @param context the caller's context
304     * @return the sequence number of the operation (for undo)
305     */
306    public int delete(Context context) {
307        ArrayList<Conversation> conversations = new ArrayList<Conversation>();
308        conversations.add(this);
309        return delete(context, conversations);
310    }
311
312    /**
313     * Mark a single conversation read/unread.
314     * @param context the caller's context
315     * @param read true for read, false for unread
316     * @return the sequence number of the operation (for undo)
317     */
318    public int markRead(Context context, boolean read) {
319        ContentValues values = new ContentValues();
320        values.put(ConversationColumns.READ, read);
321
322        return apply(
323                context,
324                getOperationsForConversations(Arrays.asList(this), ConversationOperation.UPDATE,
325                        values, true /* autoNotify */));
326    }
327
328    /**
329     * Delete a group of conversations immediately in the UI and in a single transaction in the
330     * underlying provider
331     * @param context the caller's context
332     * @param conversations a collection of conversations
333     * @return the sequence number of the operation (for undo)
334     */
335    public static int delete(Context context, Collection<Conversation> conversations) {
336        ArrayList<ConversationOperation> ops = Lists.newArrayList();
337        for (Conversation conv: conversations) {
338            ConversationOperation op =
339                    new ConversationOperation(ConversationOperation.DELETE, conv);
340            ops.add(op);
341        }
342        return apply(context, ops);
343    }
344
345    // Convenience methods
346    private static int apply(Context context, ArrayList<ConversationOperation> operations) {
347        ContentProviderClient client =
348                context.getContentResolver().acquireContentProviderClient(
349                        ConversationProvider.AUTHORITY);
350        try {
351            ConversationProvider cp = (ConversationProvider)client.getLocalContentProvider();
352            return cp.apply(operations);
353        } finally {
354            client.release();
355        }
356    }
357
358    private static void undoLocal(Context context) {
359        ContentProviderClient client =
360                context.getContentResolver().acquireContentProviderClient(
361                        ConversationProvider.AUTHORITY);
362        try {
363            ConversationProvider cp = (ConversationProvider)client.getLocalContentProvider();
364            cp.undo();
365        } finally {
366            client.release();
367        }
368    }
369
370    public static void undo(final Context context, final Uri undoUri) {
371        new Thread(new Runnable() {
372            @Override
373            public void run() {
374                Cursor c = context.getContentResolver().query(undoUri, UIProvider.UNDO_PROJECTION,
375                        null, null, null);
376                if (c != null) {
377                    c.close();
378                }
379            }
380        }).start();
381        undoLocal(context);
382    }
383
384    public static int archive(Context context, Collection<Conversation> conversations) {
385        ArrayList<ConversationOperation> ops = Lists.newArrayList();
386        for (Conversation conv: conversations) {
387            ConversationOperation op =
388                    new ConversationOperation(ConversationOperation.ARCHIVE, conv);
389            ops.add(op);
390        }
391        return apply(context, ops);
392    }
393
394    public static int mute(Context context, Collection<Conversation> conversations) {
395        ArrayList<ConversationOperation> ops = Lists.newArrayList();
396        for (Conversation conv: conversations) {
397            ConversationOperation op =
398                    new ConversationOperation(ConversationOperation.MUTE, conv);
399            ops.add(op);
400        }
401        return apply(context, ops);
402    }
403
404    public static int reportSpam(Context context, Collection<Conversation> conversations) {
405        ArrayList<ConversationOperation> ops = Lists.newArrayList();
406        for (Conversation conv: conversations) {
407            ConversationOperation op =
408                    new ConversationOperation(ConversationOperation.REPORT_SPAM, conv);
409            ops.add(op);
410        }
411        return apply(context, ops);
412    }
413}