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