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