Conversation.java revision 397621b93f83f8933f7a29a9b6d7fe2b88ec4008
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    /**
168     * Get if this conversation is marked as high priority.
169     */
170    public boolean isImportant() {
171        return priority == UIProvider.ConversationPriority.IMPORTANT;
172    }
173
174    // Below are methods that update Conversation data (update/delete)
175
176    /**
177     * Update an integer column for a single conversation (see updateBoolean below)
178     */
179    public int updateInt(Context context, String columnName, int value) {
180        return updateInt(context, Arrays.asList(this), columnName, value);
181    }
182
183    /**
184     * Update an integer column for a group of conversations (see updateValues below)
185     */
186    public static int updateInt(Context context, Collection<Conversation> conversations,
187            String columnName, int value) {
188        ContentValues cv = new ContentValues();
189        cv.put(columnName, value);
190        return updateValues(context, conversations, cv);
191    }
192
193    /**
194     * Update a boolean column for a single conversation (see updateBoolean below)
195     */
196    public int updateBoolean(Context context, String columnName, boolean value) {
197        return updateBoolean(context, Arrays.asList(this), columnName, value);
198    }
199
200    /**
201     * Update a string column for a group of conversations (see updateValues below)
202     */
203    public static int updateBoolean(Context context, Collection<Conversation> conversations,
204            String columnName, boolean value) {
205        ContentValues cv = new ContentValues();
206        cv.put(columnName, value);
207        return updateValues(context, conversations, cv);
208    }
209
210    /**
211     * Update a string column for a single conversation (see updateString below)
212     */
213    public int updateString(Context context, String columnName, String value) {
214        return updateString(context, Arrays.asList(this), columnName, value);
215    }
216
217    /**
218     * Update a string column for a group of conversations (see updateValues below)
219     */
220    public static int updateString(Context context, Collection<Conversation> conversations,
221            String columnName, String value) {
222        ContentValues cv = new ContentValues();
223        cv.put(columnName, value);
224        return updateValues(context, conversations, cv);
225    }
226
227    /**
228     * Update a boolean column for a group of conversations, immediately in the UI and in a single
229     * transaction in the underlying provider
230     * @param conversations a collection of conversations
231     * @param context the caller's context
232     * @param columnName the column to update
233     * @param value the new value
234     * @return the sequence number of the operation (for undo)
235     */
236    private static int updateValues(Context context, Collection<Conversation> conversations,
237            ContentValues values) {
238        return apply(context,
239                getOperationsForConversations(conversations, ConversationOperation.UPDATE, values));
240    }
241
242    private static ArrayList<ConversationOperation> getOperationsForConversations(
243            Collection<Conversation> conversations, int op, ContentValues values) {
244        return getOperationsForConversations(conversations, op, values, false /* autoNotify */);
245    }
246
247    private static ArrayList<ConversationOperation> getOperationsForConversations(
248            Collection<Conversation> conversations, int type, ContentValues values,
249            boolean autoNotify) {
250        final ArrayList<ConversationOperation> ops = Lists.newArrayList();
251        for (Conversation conv: conversations) {
252            ConversationOperation op = new ConversationOperation(type, conv, values, autoNotify);
253            ops.add(op);
254        }
255        return ops;
256    }
257
258    /**
259     * Delete a single conversation
260     * @param context the caller's context
261     * @return the sequence number of the operation (for undo)
262     */
263    public int delete(Context context) {
264        ArrayList<Conversation> conversations = new ArrayList<Conversation>();
265        conversations.add(this);
266        return delete(context, conversations);
267    }
268
269    /**
270     * Mark a single conversation read/unread.
271     * @param context the caller's context
272     * @param read true for read, false for unread
273     * @return the sequence number of the operation (for undo)
274     */
275    public int markRead(Context context, boolean read) {
276        ContentValues values = new ContentValues();
277        values.put(ConversationColumns.READ, read);
278
279        return apply(
280                context,
281                getOperationsForConversations(Arrays.asList(this), ConversationOperation.UPDATE,
282                        values, true /* autoNotify */));
283    }
284
285    /**
286     * Delete a group of conversations immediately in the UI and in a single transaction in the
287     * underlying provider
288     * @param context the caller's context
289     * @param conversations a collection of conversations
290     * @return the sequence number of the operation (for undo)
291     */
292    public static int delete(Context context, Collection<Conversation> conversations) {
293        ArrayList<ConversationOperation> ops = Lists.newArrayList();
294        for (Conversation conv: conversations) {
295            ConversationOperation op =
296                    new ConversationOperation(ConversationOperation.DELETE, conv);
297            ops.add(op);
298        }
299        return apply(context, ops);
300    }
301
302    // Convenience methods
303    private static int apply(Context context, ArrayList<ConversationOperation> operations) {
304        ContentProviderClient client =
305                context.getContentResolver().acquireContentProviderClient(
306                        ConversationProvider.AUTHORITY);
307        try {
308            ConversationProvider cp = (ConversationProvider)client.getLocalContentProvider();
309            return cp.apply(operations);
310        } finally {
311            client.release();
312        }
313    }
314
315    public static void undo(final Context context, final Uri undoUri) {
316        new Thread(new Runnable() {
317            @Override
318            public void run() {
319                Cursor c = context.getContentResolver().query(undoUri, UIProvider.UNDO_PROJECTION,
320                        null, null, null);
321                if (c != null) {
322                    c.close();
323                }
324            }
325        }).start();
326    }
327
328    public static int archive(Context context, Collection<Conversation> conversations) {
329        ArrayList<ConversationOperation> ops = Lists.newArrayList();
330        for (Conversation conv: conversations) {
331            ConversationOperation op =
332                    new ConversationOperation(ConversationOperation.ARCHIVE, conv);
333            ops.add(op);
334        }
335        return apply(context, ops);
336    }
337
338    public static int mute(Context context, Collection<Conversation> conversations) {
339        ArrayList<ConversationOperation> ops = Lists.newArrayList();
340        for (Conversation conv: conversations) {
341            ConversationOperation op =
342                    new ConversationOperation(ConversationOperation.MUTE, conv);
343            ops.add(op);
344        }
345        return apply(context, ops);
346    }
347
348    public static int reportSpam(Context context, Collection<Conversation> conversations) {
349        ArrayList<ConversationOperation> ops = Lists.newArrayList();
350        for (Conversation conv: conversations) {
351            ConversationOperation op =
352                    new ConversationOperation(ConversationOperation.REPORT_SPAM, conv);
353            ops.add(op);
354        }
355        return apply(context, ops);
356    }
357}