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