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