Conversation.java revision cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104
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.database.Cursor; 20import android.net.Uri; 21import android.os.Parcel; 22import android.os.Parcelable; 23import android.provider.BaseColumns; 24import android.text.TextUtils; 25 26import com.android.mail.providers.UIProvider.ConversationColumns; 27import com.google.common.collect.ImmutableList; 28 29import java.util.ArrayList; 30import java.util.Collection; 31import java.util.Collections; 32 33public class Conversation implements Parcelable { 34 public static final int NO_POSITION = -1; 35 36 private static final String EMPTY_STRING = ""; 37 38 /** 39 * @see BaseColumns#_ID 40 */ 41 public long id; 42 /** 43 * @see UIProvider.ConversationColumns#URI 44 */ 45 public Uri uri; 46 /** 47 * @see UIProvider.ConversationColumns#SUBJECT 48 */ 49 public String subject; 50 /** 51 * @see UIProvider.ConversationColumns#DATE_RECEIVED_MS 52 */ 53 public long dateMs; 54 /** 55 * @see UIProvider.ConversationColumns#SNIPPET 56 */ 57 @Deprecated 58 public String snippet; 59 /** 60 * @see UIProvider.ConversationColumns#HAS_ATTACHMENTS 61 */ 62 public boolean hasAttachments; 63 /** 64 * @see UIProvider.ConversationColumns#MESSAGE_LIST_URI 65 */ 66 public Uri messageListUri; 67 /** 68 * @see UIProvider.ConversationColumns#SENDER_INFO 69 */ 70 @Deprecated 71 public String senders; 72 /** 73 * @see UIProvider.ConversationColumns#NUM_MESSAGES 74 */ 75 private int numMessages; 76 /** 77 * @see UIProvider.ConversationColumns#NUM_DRAFTS 78 */ 79 private int numDrafts; 80 /** 81 * @see UIProvider.ConversationColumns#SENDING_STATE 82 */ 83 public int sendingState; 84 /** 85 * @see UIProvider.ConversationColumns#PRIORITY 86 */ 87 public int priority; 88 /** 89 * @see UIProvider.ConversationColumns#READ 90 */ 91 public boolean read; 92 /** 93 * @see UIProvider.ConversationColumns#STARRED 94 */ 95 public boolean starred; 96 /** 97 * @see UIProvider.ConversationColumns#RAW_FOLDERS 98 */ 99 private String rawFolders; 100 /** 101 * @see UIProvider.ConversationColumns#FLAGS 102 */ 103 public int convFlags; 104 /** 105 * @see UIProvider.ConversationColumns#PERSONAL_LEVEL 106 */ 107 public int personalLevel; 108 /** 109 * @see UIProvider.ConversationColumns#SPAM 110 */ 111 public boolean spam; 112 /** 113 * @see UIProvider.ConversationColumns#MUTED 114 */ 115 public boolean muted; 116 /** 117 * @see UIProvider.ConversationColumns#PHISHING 118 */ 119 public boolean phishing; 120 /** 121 * @see UIProvider.ConversationColumns#COLOR 122 */ 123 public int color; 124 /** 125 * @see UIProvider.ConversationColumns#ACCOUNT_URI 126 */ 127 public Uri accountUri; 128 /** 129 * @see UIProvider.ConversationColumns#CONVERSATION_INFO 130 */ 131 public ConversationInfo conversationInfo; 132 /** 133 * @see UIProvider.ConversationColumns#CONVERSATION_BASE_URI 134 */ 135 public Uri conversationBaseUri; 136 /** 137 * @see UIProvider.ConversationColumns#CONVERSATION_COOKIE 138 */ 139 public String conversationCookie; 140 /** 141 * @see UIProvider.ConversationColumns#REMOTE 142 */ 143 public boolean isRemote; 144 145 // Used within the UI to indicate the adapter position of this conversation 146 public transient int position; 147 // Used within the UI to indicate that a Conversation should be removed from 148 // the ConversationCursor when executing an update, e.g. the the 149 // Conversation is no longer in the ConversationList for the current folder, 150 // that is it's now in some other folder(s) 151 public transient boolean localDeleteOnUpdate; 152 153 private ArrayList<Folder> cachedRawFolders; 154 private ArrayList<Folder> cachedDisplayableFolders; 155 156 // Constituents of convFlags below 157 // Flag indicating that the item has been deleted, but will continue being 158 // shown in the list Delete/Archive of a mostly-dead item will NOT propagate 159 // the delete/archive, but WILL remove the item from the cursor 160 public static final int FLAG_MOSTLY_DEAD = 1 << 0; 161 162 /** An immutable, empty conversation list */ 163 public static final Collection<Conversation> EMPTY = Collections.emptyList(); 164 165 @Override 166 public int describeContents() { 167 return 0; 168 } 169 170 @Override 171 public void writeToParcel(Parcel dest, int flags) { 172 dest.writeLong(id); 173 dest.writeParcelable(uri, flags); 174 dest.writeString(subject); 175 dest.writeLong(dateMs); 176 dest.writeString(snippet); 177 dest.writeInt(hasAttachments ? 1 : 0); 178 dest.writeParcelable(messageListUri, 0); 179 dest.writeString(senders); 180 dest.writeInt(numMessages); 181 dest.writeInt(numDrafts); 182 dest.writeInt(sendingState); 183 dest.writeInt(priority); 184 dest.writeInt(read ? 1 : 0); 185 dest.writeInt(starred ? 1 : 0); 186 dest.writeString(rawFolders); 187 dest.writeInt(convFlags); 188 dest.writeInt(personalLevel); 189 dest.writeInt(spam ? 1 : 0); 190 dest.writeInt(phishing ? 1 : 0); 191 dest.writeInt(muted ? 1 : 0); 192 dest.writeInt(color); 193 dest.writeParcelable(accountUri, 0); 194 dest.writeString(ConversationInfo.toString(conversationInfo)); 195 dest.writeParcelable(conversationBaseUri, 0); 196 dest.writeString(conversationCookie); 197 dest.writeInt(isRemote ? 1 : 0); 198 } 199 200 private Conversation(Parcel in) { 201 id = in.readLong(); 202 uri = in.readParcelable(null); 203 subject = in.readString(); 204 dateMs = in.readLong(); 205 snippet = in.readString(); 206 hasAttachments = (in.readInt() != 0); 207 messageListUri = in.readParcelable(null); 208 senders = emptyIfNull(in.readString()); 209 numMessages = in.readInt(); 210 numDrafts = in.readInt(); 211 sendingState = in.readInt(); 212 priority = in.readInt(); 213 read = (in.readInt() != 0); 214 starred = (in.readInt() != 0); 215 rawFolders = in.readString(); 216 convFlags = in.readInt(); 217 personalLevel = in.readInt(); 218 spam = in.readInt() != 0; 219 phishing = in.readInt() != 0; 220 muted = in.readInt() != 0; 221 color = in.readInt(); 222 accountUri = in.readParcelable(null); 223 position = NO_POSITION; 224 localDeleteOnUpdate = false; 225 conversationInfo = ConversationInfo.fromString(in.readString()); 226 conversationBaseUri = in.readParcelable(null); 227 conversationCookie = in.readString(); 228 isRemote = in.readInt() != 0; 229 } 230 231 @Override 232 public String toString() { 233 return "[conversation id=" + id + ", subject =" + subject + "]"; 234 } 235 236 public static final Creator<Conversation> CREATOR = new Creator<Conversation>() { 237 238 @Override 239 public Conversation createFromParcel(Parcel source) { 240 return new Conversation(source); 241 } 242 243 @Override 244 public Conversation[] newArray(int size) { 245 return new Conversation[size]; 246 } 247 248 }; 249 250 public static final Uri MOVE_CONVERSATIONS_URI = Uri.parse("content://moveconversations"); 251 252 /** 253 * The column that needs to be updated to change the read state of a 254 * conversation. 255 */ 256 public static final String UPDATE_FOLDER_COLUMN = ConversationColumns.RAW_FOLDERS; 257 258 public Conversation(Cursor cursor) { 259 if (cursor != null) { 260 id = cursor.getLong(UIProvider.CONVERSATION_ID_COLUMN); 261 uri = Uri.parse(cursor.getString(UIProvider.CONVERSATION_URI_COLUMN)); 262 dateMs = cursor.getLong(UIProvider.CONVERSATION_DATE_RECEIVED_MS_COLUMN); 263 subject = cursor.getString(UIProvider.CONVERSATION_SUBJECT_COLUMN); 264 // Don't allow null subject 265 if (subject == null) { 266 subject = ""; 267 } 268 hasAttachments = cursor.getInt(UIProvider.CONVERSATION_HAS_ATTACHMENTS_COLUMN) != 0; 269 String messageList = cursor.getString(UIProvider.CONVERSATION_MESSAGE_LIST_URI_COLUMN); 270 messageListUri = !TextUtils.isEmpty(messageList) ? Uri.parse(messageList) : null; 271 sendingState = cursor.getInt(UIProvider.CONVERSATION_SENDING_STATE_COLUMN); 272 priority = cursor.getInt(UIProvider.CONVERSATION_PRIORITY_COLUMN); 273 read = cursor.getInt(UIProvider.CONVERSATION_READ_COLUMN) != 0; 274 starred = cursor.getInt(UIProvider.CONVERSATION_STARRED_COLUMN) != 0; 275 rawFolders = cursor.getString(UIProvider.CONVERSATION_RAW_FOLDERS_COLUMN); 276 convFlags = cursor.getInt(UIProvider.CONVERSATION_FLAGS_COLUMN); 277 personalLevel = cursor.getInt(UIProvider.CONVERSATION_PERSONAL_LEVEL_COLUMN); 278 spam = cursor.getInt(UIProvider.CONVERSATION_IS_SPAM_COLUMN) != 0; 279 phishing = cursor.getInt(UIProvider.CONVERSATION_IS_PHISHING_COLUMN) != 0; 280 muted = cursor.getInt(UIProvider.CONVERSATION_MUTED_COLUMN) != 0; 281 color = cursor.getInt(UIProvider.CONVERSATION_COLOR_COLUMN); 282 String account = cursor.getString(UIProvider.CONVERSATION_ACCOUNT_URI_COLUMN); 283 accountUri = !TextUtils.isEmpty(account) ? Uri.parse(account) : null; 284 position = NO_POSITION; 285 localDeleteOnUpdate = false; 286 conversationInfo = ConversationInfo.fromString(cursor 287 .getString(UIProvider.CONVERSATION_INFO_COLUMN)); 288 final String conversationBase = 289 cursor.getString(UIProvider.CONVERSATION_BASE_URI_COLUMN); 290 conversationBaseUri = !TextUtils.isEmpty(conversationBase) ? 291 Uri.parse(conversationBase) : null; 292 conversationCookie = cursor.getString(UIProvider.CONVERSATION_COOKIE_COLUMN); 293 if (conversationInfo == null) { 294 snippet = cursor.getString(UIProvider.CONVERSATION_SNIPPET_COLUMN); 295 senders = emptyIfNull(cursor.getString(UIProvider.CONVERSATION_SENDER_INFO_COLUMN)); 296 numMessages = cursor.getInt(UIProvider.CONVERSATION_NUM_MESSAGES_COLUMN); 297 numDrafts = cursor.getInt(UIProvider.CONVERSATION_NUM_DRAFTS_COLUMN); 298 } 299 isRemote = cursor.getInt(UIProvider.CONVERSATION_REMOTE_COLUMN) != 0; 300 } 301 } 302 303 public Conversation() { 304 } 305 306 public static Conversation create(long id, Uri uri, String subject, long dateMs, 307 String snippet, boolean hasAttachment, Uri messageListUri, String senders, 308 int numMessages, int numDrafts, int sendingState, int priority, boolean read, 309 boolean starred, String rawFolders, int convFlags, int personalLevel, boolean spam, 310 boolean phishing, boolean muted, Uri accountUri, ConversationInfo conversationInfo, 311 Uri conversationBase, String conversationCookie, boolean isRemote) { 312 313 final Conversation conversation = new Conversation(); 314 315 conversation.id = id; 316 conversation.uri = uri; 317 conversation.subject = subject; 318 conversation.dateMs = dateMs; 319 conversation.snippet = snippet; 320 conversation.hasAttachments = hasAttachment; 321 conversation.messageListUri = messageListUri; 322 conversation.senders = emptyIfNull(senders); 323 conversation.numMessages = numMessages; 324 conversation.numDrafts = numDrafts; 325 conversation.sendingState = sendingState; 326 conversation.priority = priority; 327 conversation.read = read; 328 conversation.starred = starred; 329 conversation.rawFolders = rawFolders; 330 conversation.convFlags = convFlags; 331 conversation.personalLevel = personalLevel; 332 conversation.spam = spam; 333 conversation.phishing = phishing; 334 conversation.muted = muted; 335 conversation.color = 0; 336 conversation.accountUri = accountUri; 337 conversation.conversationInfo = conversationInfo; 338 conversation.conversationBaseUri = conversationBase; 339 conversation.conversationCookie = conversationCookie; 340 conversation.isRemote = isRemote; 341 return conversation; 342 } 343 344 public ArrayList<Folder> getRawFolders() { 345 if (cachedRawFolders == null) { 346 // Create cached folders. 347 if (!TextUtils.isEmpty(rawFolders)) { 348 cachedRawFolders = Folder.getFoldersArray(rawFolders); 349 } else { 350 return new ArrayList<Folder>(); 351 } 352 } 353 return cachedRawFolders; 354 } 355 356 public void setRawFolders(String raw) { 357 clearCachedFolders(); 358 rawFolders = raw; 359 } 360 361 public String getRawFoldersString() { 362 return rawFolders; 363 } 364 365 private void clearCachedFolders() { 366 cachedRawFolders = null; 367 cachedDisplayableFolders = null; 368 } 369 370 public ArrayList<Folder> getRawFoldersForDisplay(Folder ignoreFolder) { 371 ArrayList<Folder> folders = getRawFolders(); 372 if (cachedDisplayableFolders == null) { 373 cachedDisplayableFolders = new ArrayList<Folder>(); 374 for (Folder folder : folders) { 375 // skip the ignoreFolder 376 if (ignoreFolder != null && ignoreFolder.equals(folder)) { 377 continue; 378 } 379 cachedDisplayableFolders.add(folder); 380 } 381 } 382 return cachedDisplayableFolders; 383 } 384 385 @Override 386 public boolean equals(Object o) { 387 if (o instanceof Conversation) { 388 Conversation conv = (Conversation) o; 389 return conv.uri.equals(uri); 390 } 391 return false; 392 } 393 394 @Override 395 public int hashCode() { 396 return uri.hashCode(); 397 } 398 399 /** 400 * Get if this conversation is marked as high priority. 401 */ 402 public boolean isImportant() { 403 return priority == UIProvider.ConversationPriority.IMPORTANT; 404 } 405 406 /** 407 * Get if this conversation is mostly dead 408 */ 409 public boolean isMostlyDead() { 410 return (convFlags & FLAG_MOSTLY_DEAD) != 0; 411 } 412 413 /** 414 * Returns true if the URI of the conversation specified as the needle was 415 * found in the collection of conversations specified as the haystack. False 416 * otherwise. This method is safe to call with null arguments. 417 * 418 * @param haystack 419 * @param needle 420 * @return true if the needle was found in the haystack, false otherwise. 421 */ 422 public final static boolean contains(Collection<Conversation> haystack, Conversation needle) { 423 // If the haystack is empty, it cannot contain anything. 424 if (haystack == null || haystack.size() <= 0) { 425 return false; 426 } 427 // The null folder exists everywhere. 428 if (needle == null) { 429 return true; 430 } 431 final long toFind = needle.id; 432 for (final Conversation c : haystack) { 433 if (toFind == c.id) { 434 return true; 435 } 436 } 437 return false; 438 } 439 440 /** 441 * Returns a collection of a single conversation. This method always returns 442 * a valid collection even if the input conversation is null. 443 * 444 * @param in a conversation, possibly null. 445 * @return a collection of the conversation. 446 */ 447 public static Collection<Conversation> listOf(Conversation in) { 448 final Collection<Conversation> target = (in == null) ? EMPTY : ImmutableList.of(in); 449 return target; 450 } 451 452 /** 453 * Get the snippet for this conversation. Masks that it may come from 454 * conversation info or the original deprecated snippet string. 455 */ 456 public String getSnippet() { 457 return conversationInfo != null && !TextUtils.isEmpty(conversationInfo.firstSnippet) ? 458 conversationInfo.firstSnippet : snippet; 459 } 460 461 /** 462 * Get the number of messages for this conversation. 463 */ 464 public int getNumMessages() { 465 return conversationInfo != null ? conversationInfo.messageCount : numMessages; 466 } 467 468 /** 469 * Get the number of drafts for this conversation. 470 */ 471 public int numDrafts() { 472 return conversationInfo != null ? conversationInfo.draftCount : numDrafts; 473 } 474 475 /** 476 * Create a human-readable string of all the conversations 477 * @param collection Any collection of conversations 478 * @return string with a human readable representation of the conversations. 479 */ 480 public static String toString(Collection<Conversation> collection) { 481 final StringBuilder out = new StringBuilder(collection.size() + " conversations:"); 482 int count = 0; 483 for (final Conversation c : collection) { 484 count++; 485 // Indent the conversations to make them easy to read in debug 486 // output. 487 out.append(" " + count + ": " + c.toString() + "\n"); 488 } 489 return out.toString(); 490 } 491 492 /** 493 * Returns an empty string if the specified string is null 494 */ 495 private static String emptyIfNull(String in) { 496 return in != null ? in : EMPTY_STRING; 497 } 498} 499