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