Conversation.java revision cf164d64bcb1da92b427bda99b97f7ec310ef704
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 // Constituents of convFlags below 69 // Flag indicating that the item has been deleted, but will continue being shown in the list 70 // Delete/Archive of a mostly-dead item will NOT propagate the delete/archive, but WILL remove 71 // the item from the cursor 72 public static final int FLAG_MOSTLY_DEAD = 1 << 0; 73 74 @Override 75 public int describeContents() { 76 return 0; 77 } 78 79 @Override 80 public void writeToParcel(Parcel dest, int flags) { 81 dest.writeLong(id); 82 dest.writeParcelable(uri, flags); 83 dest.writeString(subject); 84 dest.writeLong(dateMs); 85 dest.writeString(snippet); 86 dest.writeByte(hasAttachments ? (byte) 1 : 0); 87 dest.writeParcelable(messageListUri, 0); 88 dest.writeString(senders); 89 dest.writeInt(numMessages); 90 dest.writeInt(numDrafts); 91 dest.writeInt(sendingState); 92 dest.writeInt(priority); 93 dest.writeByte(read ? (byte) 1 : 0); 94 dest.writeByte(starred ? (byte) 1 : 0); 95 dest.writeString(folderList); 96 dest.writeString(rawFolders); 97 dest.writeInt(convFlags); 98 dest.writeInt(personalLevel); 99 dest.writeInt(spam ? 1 : 0); 100 dest.writeInt(muted ? 1 : 0); 101 } 102 103 private Conversation(Parcel in) { 104 id = in.readLong(); 105 uri = in.readParcelable(null); 106 subject = in.readString(); 107 dateMs = in.readLong(); 108 snippet = in.readString(); 109 hasAttachments = (in.readByte() != 0); 110 messageListUri = in.readParcelable(null); 111 senders = in.readString(); 112 numMessages = in.readInt(); 113 numDrafts = in.readInt(); 114 sendingState = in.readInt(); 115 priority = in.readInt(); 116 read = (in.readByte() != 0); 117 starred = (in.readByte() != 0); 118 folderList = in.readString(); 119 rawFolders = in.readString(); 120 convFlags = in.readInt(); 121 personalLevel = in.readInt(); 122 spam = in.readInt() != 0; 123 muted = in.readInt() != 0; 124 position = NO_POSITION; 125 localDeleteOnUpdate = false; 126 } 127 128 @Override 129 public String toString() { 130 return "[conversation id=" + id + "]"; 131 } 132 133 public static final Creator<Conversation> CREATOR = new Creator<Conversation>() { 134 135 @Override 136 public Conversation createFromParcel(Parcel source) { 137 return new Conversation(source); 138 } 139 140 @Override 141 public Conversation[] newArray(int size) { 142 return new Conversation[size]; 143 } 144 145 }; 146 147 public static final Uri MOVE_CONVERSATIONS_URI = Uri.parse("content://moveconversations"); 148 149 public Conversation(Cursor cursor) { 150 if (cursor != null) { 151 id = cursor.getLong(UIProvider.CONVERSATION_ID_COLUMN); 152 uri = Uri.parse(cursor.getString(UIProvider.CONVERSATION_URI_COLUMN)); 153 dateMs = cursor.getLong(UIProvider.CONVERSATION_DATE_RECEIVED_MS_COLUMN); 154 subject = cursor.getString(UIProvider.CONVERSATION_SUBJECT_COLUMN); 155 // Don't allow null subject 156 if (subject == null) { 157 subject = ""; 158 } 159 snippet = cursor.getString(UIProvider.CONVERSATION_SNIPPET_COLUMN); 160 hasAttachments = cursor.getInt(UIProvider.CONVERSATION_HAS_ATTACHMENTS_COLUMN) != 0; 161 String messageList = cursor 162 .getString(UIProvider.CONVERSATION_MESSAGE_LIST_URI_COLUMN); 163 messageListUri = !TextUtils.isEmpty(messageList) ? Uri.parse(messageList) : null; 164 senders = cursor.getString(UIProvider.CONVERSATION_SENDER_INFO_COLUMN); 165 numMessages = cursor.getInt(UIProvider.CONVERSATION_NUM_MESSAGES_COLUMN); 166 numDrafts = cursor.getInt(UIProvider.CONVERSATION_NUM_DRAFTS_COLUMN); 167 sendingState = cursor.getInt(UIProvider.CONVERSATION_SENDING_STATE_COLUMN); 168 priority = cursor.getInt(UIProvider.CONVERSATION_PRIORITY_COLUMN); 169 read = cursor.getInt(UIProvider.CONVERSATION_READ_COLUMN) != 0; 170 starred = cursor.getInt(UIProvider.CONVERSATION_STARRED_COLUMN) != 0; 171 folderList = cursor.getString(UIProvider.CONVERSATION_FOLDER_LIST_COLUMN); 172 rawFolders = cursor.getString(UIProvider.CONVERSATION_RAW_FOLDERS_COLUMN); 173 convFlags = cursor.getInt(UIProvider.CONVERSATION_FLAGS_COLUMN); 174 personalLevel = cursor.getInt(UIProvider.CONVERSATION_PERSONAL_LEVEL_COLUMN); 175 spam = cursor.getInt(UIProvider.CONVERSATION_IS_SPAM_COLUMN) != 0; 176 muted = cursor.getInt(UIProvider.CONVERSATION_MUTED_COLUMN) != 0; 177 position = NO_POSITION; 178 localDeleteOnUpdate = false; 179 } 180 } 181 182 private Conversation() { 183 } 184 185 public static Conversation create(long id, Uri uri, String subject, long dateMs, 186 String snippet, boolean hasAttachment, Uri messageListUri, String senders, 187 int numMessages, int numDrafts, int sendingState, int priority, boolean read, 188 boolean starred, String folderList, String rawFolders, int convFlags, 189 int personalLevel, boolean spam, boolean muted) { 190 191 final Conversation conversation = new Conversation(); 192 193 conversation.id = id; 194 conversation.uri = uri; 195 conversation.subject = subject; 196 conversation.dateMs = dateMs; 197 conversation.snippet = snippet; 198 conversation.hasAttachments = hasAttachment; 199 conversation.messageListUri = messageListUri; 200 conversation.senders = senders; 201 conversation.numMessages = numMessages; 202 conversation.numDrafts = numDrafts; 203 conversation.sendingState = sendingState; 204 conversation.priority = priority; 205 conversation.read = read; 206 conversation.starred = starred; 207 conversation.folderList = folderList; 208 conversation.rawFolders = rawFolders; 209 conversation.convFlags = convFlags; 210 conversation.personalLevel = personalLevel; 211 conversation.spam = spam; 212 conversation.muted = muted; 213 return conversation; 214 } 215 216 @Override 217 public boolean equals(Object o) { 218 return uri.equals(o); 219 } 220 221 @Override 222 public int hashCode() { 223 return uri.hashCode(); 224 } 225 226 /** 227 * Get if this conversation is marked as high priority. 228 */ 229 public boolean isImportant() { 230 return priority == UIProvider.ConversationPriority.IMPORTANT; 231 } 232 233 /** 234 * Get if this conversation is mostly dead 235 */ 236 public boolean isMostlyDead() { 237 return (convFlags & FLAG_MOSTLY_DEAD) != 0; 238 } 239 240 // Below are methods that update Conversation data (update/delete) 241 242 /** 243 * Update an integer column for a single conversation (see updateBoolean below) 244 */ 245 public int updateInt(Context context, String columnName, int value) { 246 return updateInt(context, Arrays.asList(this), columnName, value); 247 } 248 249 /** 250 * Update an integer column for a group of conversations (see updateValues below) 251 */ 252 public static int updateInt(Context context, Collection<Conversation> conversations, 253 String columnName, int value) { 254 ContentValues cv = new ContentValues(); 255 cv.put(columnName, value); 256 return updateValues(context, conversations, cv); 257 } 258 259 /** 260 * Update a boolean column for a single conversation (see updateBoolean below) 261 */ 262 public int updateBoolean(Context context, String columnName, boolean value) { 263 return updateBoolean(context, Arrays.asList(this), columnName, value); 264 } 265 266 /** 267 * Update a string column for a group of conversations (see updateValues below) 268 */ 269 public static int updateBoolean(Context context, Collection<Conversation> conversations, 270 String columnName, boolean value) { 271 ContentValues cv = new ContentValues(); 272 cv.put(columnName, value); 273 return updateValues(context, conversations, cv); 274 } 275 276 /** 277 * Update a string column for a single conversation (see updateString below) 278 */ 279 public int updateString(Context context, String columnName, String value) { 280 return updateString(context, Arrays.asList(this), columnName, value); 281 } 282 283 /** 284 * Update a string column for a group of conversations (see updateValues below) 285 */ 286 public static int updateString(Context context, Collection<Conversation> conversations, 287 String columnName, String value) { 288 ContentValues cv = new ContentValues(); 289 cv.put(columnName, value); 290 return updateValues(context, conversations, cv); 291 } 292 293 /** 294 * Update a boolean column for a group of conversations, immediately in the UI and in a single 295 * transaction in the underlying provider 296 * @param conversations a collection of conversations 297 * @param context the caller's context 298 * @param columnName the column to update 299 * @param value the new value 300 * @return the sequence number of the operation (for undo) 301 */ 302 private static int updateValues(Context context, Collection<Conversation> conversations, 303 ContentValues values) { 304 return apply(context, 305 getOperationsForConversations(conversations, ConversationOperation.UPDATE, values)); 306 } 307 308 private static ArrayList<ConversationOperation> getOperationsForConversations( 309 Collection<Conversation> conversations, int op, ContentValues values) { 310 return getOperationsForConversations(conversations, op, values, false /* autoNotify */); 311 } 312 313 private static ArrayList<ConversationOperation> getOperationsForConversations( 314 Collection<Conversation> conversations, int type, ContentValues values, 315 boolean autoNotify) { 316 final ArrayList<ConversationOperation> ops = Lists.newArrayList(); 317 for (Conversation conv: conversations) { 318 ConversationOperation op = new ConversationOperation(type, conv, values, autoNotify); 319 ops.add(op); 320 } 321 return ops; 322 } 323 324 /** 325 * Delete a single conversation 326 * @param context the caller's context 327 * @return the sequence number of the operation (for undo) 328 */ 329 public int delete(Context context) { 330 ArrayList<Conversation> conversations = new ArrayList<Conversation>(); 331 conversations.add(this); 332 return delete(context, conversations); 333 } 334 335 /** 336 * Delete a single conversation 337 * @param context the caller's context 338 * @return the sequence number of the operation (for undo) 339 */ 340 public int mostlyArchive(Context context) { 341 ArrayList<Conversation> conversations = new ArrayList<Conversation>(); 342 conversations.add(this); 343 return archive(context, conversations); 344 } 345 346 /** 347 * Delete a single conversation 348 * @param context the caller's context 349 * @return the sequence number of the operation (for undo) 350 */ 351 public int mostlyDelete(Context context) { 352 ArrayList<Conversation> conversations = new ArrayList<Conversation>(); 353 conversations.add(this); 354 return delete(context, conversations); 355 } 356 357 /** 358 * Mark a single conversation read/unread. 359 * @param context the caller's context 360 * @param read true for read, false for unread 361 * @return the sequence number of the operation (for undo) 362 */ 363 public int markRead(Context context, boolean read) { 364 ContentValues values = new ContentValues(); 365 values.put(ConversationColumns.READ, read); 366 367 return apply( 368 context, 369 getOperationsForConversations(Arrays.asList(this), ConversationOperation.UPDATE, 370 values, true /* autoNotify */)); 371 } 372 373 // Convenience methods 374 private static int apply(Context context, ArrayList<ConversationOperation> operations) { 375 ContentProviderClient client = 376 context.getContentResolver().acquireContentProviderClient( 377 ConversationProvider.AUTHORITY); 378 try { 379 ConversationProvider cp = (ConversationProvider)client.getLocalContentProvider(); 380 return cp.apply(operations); 381 } finally { 382 client.release(); 383 } 384 } 385 386 private static void undoLocal(Context context) { 387 ContentProviderClient client = 388 context.getContentResolver().acquireContentProviderClient( 389 ConversationProvider.AUTHORITY); 390 try { 391 ConversationProvider cp = (ConversationProvider)client.getLocalContentProvider(); 392 cp.undo(); 393 } finally { 394 client.release(); 395 } 396 } 397 398 public static void undo(final Context context, final Uri undoUri) { 399 new Thread(new Runnable() { 400 @Override 401 public void run() { 402 Cursor c = context.getContentResolver().query(undoUri, UIProvider.UNDO_PROJECTION, 403 null, null, null); 404 if (c != null) { 405 c.close(); 406 } 407 } 408 }).start(); 409 undoLocal(context); 410 } 411 412 /** 413 * Delete a group of conversations immediately in the UI and in a single transaction in the 414 * underlying provider. See applyAction for argument descriptions 415 */ 416 public static int delete(Context context, Collection<Conversation> conversations) { 417 return applyAction(context, conversations, ConversationOperation.DELETE); 418 } 419 420 /** 421 * As above, for archive 422 */ 423 public static int archive(Context context, Collection<Conversation> conversations) { 424 return applyAction(context, conversations, ConversationOperation.ARCHIVE); 425 } 426 427 /** 428 * As above, for mute 429 */ 430 public static int mute(Context context, Collection<Conversation> conversations) { 431 return applyAction(context, conversations, ConversationOperation.MUTE); 432 } 433 434 /** 435 * As above, for report spam 436 */ 437 public static int reportSpam(Context context, Collection<Conversation> conversations) { 438 return applyAction(context, conversations, ConversationOperation.REPORT_SPAM); 439 } 440 441 /** 442 * As above, for mostly archive 443 */ 444 public static int mostlyArchive(Context context, Collection<Conversation> conversations) { 445 return applyAction(context, conversations, ConversationOperation.MOSTLY_ARCHIVE); 446 } 447 448 /** 449 * As above, for mostly delete 450 */ 451 public static int mostlyDelete(Context context, Collection<Conversation> conversations) { 452 return applyAction(context, conversations, ConversationOperation.MOSTLY_DELETE); 453 } 454 455 /** 456 * Convenience method for performing an operation on a group of conversations 457 * @param context the caller's context 458 * @param conversations the conversations to be affected 459 * @param opAction the action to take 460 * @return the sequence number of the operation applied in CC 461 */ 462 private static int applyAction(Context context, Collection<Conversation> conversations, 463 int opAction) { 464 ArrayList<ConversationOperation> ops = Lists.newArrayList(); 465 for (Conversation conv: conversations) { 466 ConversationOperation op = 467 new ConversationOperation(opAction, conv); 468 ops.add(op); 469 } 470 return apply(context, ops); 471 } 472}