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