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