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