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 17 package com.android.mail.providers; 18 19 import android.content.ContentValues; 20 import android.content.Context; 21 import android.database.Cursor; 22 import android.net.Uri; 23 import android.os.Bundle; 24 import android.os.Parcel; 25 import android.os.Parcelable; 26 import android.provider.BaseColumns; 27 import android.text.TextUtils; 28 29 import com.android.mail.R; 30 import com.android.mail.browse.ConversationCursor; 31 import com.android.mail.content.CursorCreator; 32 import com.android.mail.providers.UIProvider.ConversationColumns; 33 import com.android.mail.ui.ConversationCursorLoader; 34 import com.android.mail.utils.LogTag; 35 import com.android.mail.utils.LogUtils; 36 import com.google.common.collect.ImmutableList; 37 import com.google.common.collect.Lists; 38 39 import java.util.ArrayList; 40 import java.util.Collection; 41 import java.util.Collections; 42 import java.util.List; 43 44 public class Conversation implements Parcelable { 45 public static final int NO_POSITION = -1; 46 47 private static final String LOG_TAG = LogTag.getLogTag(); 48 49 private static final String EMPTY_STRING = ""; 50 51 /** 52 * @see BaseColumns#_ID 53 */ 54 public long id; 55 /** 56 * @see UIProvider.ConversationColumns#URI 57 */ 58 public Uri uri; 59 /** 60 * @see UIProvider.ConversationColumns#SUBJECT 61 */ 62 public String subject; 63 /** 64 * @see UIProvider.ConversationColumns#DATE_RECEIVED_MS 65 */ 66 public long dateMs; 67 /** 68 * @see UIProvider.ConversationColumns#SNIPPET 69 */ 70 @Deprecated 71 public String snippet; 72 /** 73 * @see UIProvider.ConversationColumns#HAS_ATTACHMENTS 74 */ 75 public boolean hasAttachments; 76 /** 77 * Union of attachmentPreviewUri0 and attachmentPreviewUri1 78 */ 79 public transient ArrayList<String> attachmentPreviews; 80 /** 81 * @see UIProvider.ConversationColumns#ATTACHMENT_PREVIEW_URI0 82 */ 83 public String attachmentPreviewUri0; 84 /** 85 * @see UIProvider.ConversationColumns#ATTACHMENT_PREVIEW_URI1 86 */ 87 public String attachmentPreviewUri1; 88 /** 89 * @see UIProvider.ConversationColumns#ATTACHMENT_PREVIEW_STATES 90 */ 91 public int attachmentPreviewStates; 92 /** 93 * @see UIProvider.ConversationColumns#ATTACHMENT_PREVIEWS_COUNT 94 */ 95 public int attachmentPreviewsCount; 96 /** 97 * @see UIProvider.ConversationColumns#MESSAGE_LIST_URI 98 */ 99 public Uri messageListUri; 100 /** 101 * @see UIProvider.ConversationColumns#SENDER_INFO 102 */ 103 @Deprecated 104 public String senders; 105 /** 106 * @see UIProvider.ConversationColumns#NUM_MESSAGES 107 */ 108 private int numMessages; 109 /** 110 * @see UIProvider.ConversationColumns#NUM_DRAFTS 111 */ 112 private int numDrafts; 113 /** 114 * @see UIProvider.ConversationColumns#SENDING_STATE 115 */ 116 public int sendingState; 117 /** 118 * @see UIProvider.ConversationColumns#PRIORITY 119 */ 120 public int priority; 121 /** 122 * @see UIProvider.ConversationColumns#READ 123 */ 124 public boolean read; 125 /** 126 * @see UIProvider.ConversationColumns#SEEN 127 */ 128 public boolean seen; 129 /** 130 * @see UIProvider.ConversationColumns#STARRED 131 */ 132 public boolean starred; 133 /** 134 * @see UIProvider.ConversationColumns#RAW_FOLDERS 135 */ 136 private FolderList rawFolders; 137 /** 138 * @see UIProvider.ConversationColumns#FLAGS 139 */ 140 public int convFlags; 141 /** 142 * @see UIProvider.ConversationColumns#PERSONAL_LEVEL 143 */ 144 public int personalLevel; 145 /** 146 * @see UIProvider.ConversationColumns#SPAM 147 */ 148 public boolean spam; 149 /** 150 * @see UIProvider.ConversationColumns#MUTED 151 */ 152 public boolean muted; 153 /** 154 * @see UIProvider.ConversationColumns#PHISHING 155 */ 156 public boolean phishing; 157 /** 158 * @see UIProvider.ConversationColumns#COLOR 159 */ 160 public int color; 161 /** 162 * @see UIProvider.ConversationColumns#ACCOUNT_URI 163 */ 164 public Uri accountUri; 165 /** 166 * @see UIProvider.ConversationColumns#CONVERSATION_INFO 167 */ 168 public ConversationInfo conversationInfo; 169 /** 170 * @see UIProvider.ConversationColumns#CONVERSATION_BASE_URI 171 */ 172 public Uri conversationBaseUri; 173 /** 174 * @see UIProvider.ConversationColumns#REMOTE 175 */ 176 public boolean isRemote; 177 178 /** 179 * Used within the UI to indicate the adapter position of this conversation 180 * 181 * @deprecated Keeping this in sync with the desired value is a not always done properly, is a 182 * source of bugs, and is a bad idea in general. Do not trust this value. Try to 183 * migrate code away from using it. 184 */ 185 @Deprecated 186 public transient int position; 187 // Used within the UI to indicate that a Conversation should be removed from 188 // the ConversationCursor when executing an update, e.g. the the 189 // Conversation is no longer in the ConversationList for the current folder, 190 // that is it's now in some other folder(s) 191 public transient boolean localDeleteOnUpdate; 192 193 private transient boolean viewed; 194 195 private static String sSubjectAndSnippet; 196 197 // Constituents of convFlags below 198 // Flag indicating that the item has been deleted, but will continue being 199 // shown in the list Delete/Archive of a mostly-dead item will NOT propagate 200 // the delete/archive, but WILL remove the item from the cursor 201 public static final int FLAG_MOSTLY_DEAD = 1 << 0; 202 203 /** An immutable, empty conversation list */ 204 public static final Collection<Conversation> EMPTY = Collections.emptyList(); 205 206 @Override 207 public int describeContents() { 208 return 0; 209 } 210 211 @Override 212 public void writeToParcel(Parcel dest, int flags) { 213 dest.writeLong(id); 214 dest.writeParcelable(uri, flags); 215 dest.writeString(subject); 216 dest.writeLong(dateMs); 217 dest.writeString(snippet); 218 dest.writeInt(hasAttachments ? 1 : 0); 219 dest.writeParcelable(messageListUri, 0); 220 dest.writeString(senders); 221 dest.writeInt(numMessages); 222 dest.writeInt(numDrafts); 223 dest.writeInt(sendingState); 224 dest.writeInt(priority); 225 dest.writeInt(read ? 1 : 0); 226 dest.writeInt(seen ? 1 : 0); 227 dest.writeInt(starred ? 1 : 0); 228 dest.writeParcelable(rawFolders, 0); 229 dest.writeInt(convFlags); 230 dest.writeInt(personalLevel); 231 dest.writeInt(spam ? 1 : 0); 232 dest.writeInt(phishing ? 1 : 0); 233 dest.writeInt(muted ? 1 : 0); 234 dest.writeInt(color); 235 dest.writeParcelable(accountUri, 0); 236 dest.writeParcelable(conversationInfo, 0); 237 dest.writeParcelable(conversationBaseUri, 0); 238 dest.writeInt(isRemote ? 1 : 0); 239 dest.writeString(attachmentPreviewUri0); 240 dest.writeString(attachmentPreviewUri1); 241 dest.writeInt(attachmentPreviewStates); 242 dest.writeInt(attachmentPreviewsCount); 243 } 244 245 private Conversation(Parcel in, ClassLoader loader) { 246 id = in.readLong(); 247 uri = in.readParcelable(null); 248 subject = in.readString(); 249 dateMs = in.readLong(); 250 snippet = in.readString(); 251 hasAttachments = (in.readInt() != 0); 252 messageListUri = in.readParcelable(null); 253 senders = emptyIfNull(in.readString()); 254 numMessages = in.readInt(); 255 numDrafts = in.readInt(); 256 sendingState = in.readInt(); 257 priority = in.readInt(); 258 read = (in.readInt() != 0); 259 seen = (in.readInt() != 0); 260 starred = (in.readInt() != 0); 261 rawFolders = in.readParcelable(loader); 262 convFlags = in.readInt(); 263 personalLevel = in.readInt(); 264 spam = in.readInt() != 0; 265 phishing = in.readInt() != 0; 266 muted = in.readInt() != 0; 267 color = in.readInt(); 268 accountUri = in.readParcelable(null); 269 position = NO_POSITION; 270 localDeleteOnUpdate = false; 271 conversationInfo = in.readParcelable(loader); 272 conversationBaseUri = in.readParcelable(null); 273 isRemote = in.readInt() != 0; 274 attachmentPreviews = null; 275 attachmentPreviewUri0 = in.readString(); 276 attachmentPreviewUri1 = in.readString(); 277 attachmentPreviewStates = in.readInt(); 278 attachmentPreviewsCount = in.readInt(); 279 } 280 281 @Override 282 public String toString() { 283 // log extra info at DEBUG level or finer 284 final StringBuilder sb = new StringBuilder("[conversation id="); 285 sb.append(id); 286 if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) { 287 sb.append(", subject="); 288 sb.append(subject); 289 } 290 sb.append("]"); 291 return sb.toString(); 292 } 293 294 public static final ClassLoaderCreator<Conversation> CREATOR = 295 new ClassLoaderCreator<Conversation>() { 296 297 @Override 298 public Conversation createFromParcel(Parcel source) { 299 return new Conversation(source, null); 300 } 301 302 @Override 303 public Conversation createFromParcel(Parcel source, ClassLoader loader) { 304 return new Conversation(source, loader); 305 } 306 307 @Override 308 public Conversation[] newArray(int size) { 309 return new Conversation[size]; 310 } 311 312 }; 313 314 public static final Uri MOVE_CONVERSATIONS_URI = Uri.parse("content://moveconversations"); 315 316 /** 317 * The column that needs to be updated to change the folders for a conversation. 318 */ 319 public static final String UPDATE_FOLDER_COLUMN = ConversationColumns.RAW_FOLDERS; 320 321 public Conversation(Cursor cursor) { 322 if (cursor != null) { 323 id = cursor.getLong(UIProvider.CONVERSATION_ID_COLUMN); 324 uri = Uri.parse(cursor.getString(UIProvider.CONVERSATION_URI_COLUMN)); 325 dateMs = cursor.getLong(UIProvider.CONVERSATION_DATE_RECEIVED_MS_COLUMN); 326 subject = cursor.getString(UIProvider.CONVERSATION_SUBJECT_COLUMN); 327 // Don't allow null subject 328 if (subject == null) { 329 subject = ""; 330 } 331 hasAttachments = cursor.getInt(UIProvider.CONVERSATION_HAS_ATTACHMENTS_COLUMN) != 0; 332 String messageList = cursor.getString(UIProvider.CONVERSATION_MESSAGE_LIST_URI_COLUMN); 333 messageListUri = !TextUtils.isEmpty(messageList) ? Uri.parse(messageList) : null; 334 sendingState = cursor.getInt(UIProvider.CONVERSATION_SENDING_STATE_COLUMN); 335 priority = cursor.getInt(UIProvider.CONVERSATION_PRIORITY_COLUMN); 336 read = cursor.getInt(UIProvider.CONVERSATION_READ_COLUMN) != 0; 337 seen = cursor.getInt(UIProvider.CONVERSATION_SEEN_COLUMN) != 0; 338 starred = cursor.getInt(UIProvider.CONVERSATION_STARRED_COLUMN) != 0; 339 rawFolders = readRawFolders(cursor); 340 convFlags = cursor.getInt(UIProvider.CONVERSATION_FLAGS_COLUMN); 341 personalLevel = cursor.getInt(UIProvider.CONVERSATION_PERSONAL_LEVEL_COLUMN); 342 spam = cursor.getInt(UIProvider.CONVERSATION_IS_SPAM_COLUMN) != 0; 343 phishing = cursor.getInt(UIProvider.CONVERSATION_IS_PHISHING_COLUMN) != 0; 344 muted = cursor.getInt(UIProvider.CONVERSATION_MUTED_COLUMN) != 0; 345 color = cursor.getInt(UIProvider.CONVERSATION_COLOR_COLUMN); 346 String account = cursor.getString(UIProvider.CONVERSATION_ACCOUNT_URI_COLUMN); 347 accountUri = !TextUtils.isEmpty(account) ? Uri.parse(account) : null; 348 position = NO_POSITION; 349 localDeleteOnUpdate = false; 350 conversationInfo = readConversationInfo(cursor); 351 final String conversationBase = 352 cursor.getString(UIProvider.CONVERSATION_BASE_URI_COLUMN); 353 conversationBaseUri = !TextUtils.isEmpty(conversationBase) ? 354 Uri.parse(conversationBase) : null; 355 if (conversationInfo == null) { 356 snippet = cursor.getString(UIProvider.CONVERSATION_SNIPPET_COLUMN); 357 senders = emptyIfNull(cursor.getString(UIProvider.CONVERSATION_SENDER_INFO_COLUMN)); 358 numMessages = cursor.getInt(UIProvider.CONVERSATION_NUM_MESSAGES_COLUMN); 359 numDrafts = cursor.getInt(UIProvider.CONVERSATION_NUM_DRAFTS_COLUMN); 360 } 361 isRemote = cursor.getInt(UIProvider.CONVERSATION_REMOTE_COLUMN) != 0; 362 attachmentPreviews = null; 363 attachmentPreviewUri0 = cursor.getString( 364 UIProvider.CONVERSATION_ATTACHMENT_PREVIEW_URI0_COLUMN); 365 attachmentPreviewUri1 = cursor.getString( 366 UIProvider.CONVERSATION_ATTACHMENT_PREVIEW_URI1_COLUMN); 367 attachmentPreviewStates = cursor.getInt( 368 UIProvider.CONVERSATION_ATTACHMENT_PREVIEW_STATES_COLUMN); 369 attachmentPreviewsCount = cursor.getInt( 370 UIProvider.CONVERSATION_ATTACHMENT_PREVIEWS_COUNT_COLUMN); 371 } 372 } 373 374 public Conversation(Conversation other) { 375 if (other == null) { 376 return; 377 } 378 379 id = other.id; 380 uri = other.uri; 381 dateMs = other.dateMs; 382 subject = other.subject; 383 hasAttachments = other.hasAttachments; 384 messageListUri = other.messageListUri; 385 sendingState = other.sendingState; 386 priority = other.priority; 387 read = other.read; 388 seen = other.seen; 389 starred = other.starred; 390 rawFolders = other.rawFolders; // FolderList is immutable, shallow copy is OK 391 convFlags = other.convFlags; 392 personalLevel = other.personalLevel; 393 spam = other.spam; 394 phishing = other.phishing; 395 muted = other.muted; 396 color = other.color; 397 accountUri = other.accountUri; 398 position = other.position; 399 localDeleteOnUpdate = other.localDeleteOnUpdate; 400 // although ConversationInfo is mutable (see ConversationInfo.markRead), applyCachedValues 401 // will overwrite this if cached changes exist anyway, so a shallow copy is OK 402 conversationInfo = other.conversationInfo; 403 conversationBaseUri = other.conversationBaseUri; 404 snippet = other.snippet; 405 senders = other.senders; 406 numMessages = other.numMessages; 407 numDrafts = other.numDrafts; 408 isRemote = other.isRemote; 409 attachmentPreviews = null; 410 attachmentPreviewUri0 = other.attachmentPreviewUri0; 411 attachmentPreviewUri1 = other.attachmentPreviewUri1; 412 attachmentPreviewStates = other.attachmentPreviewStates; 413 attachmentPreviewsCount = other.attachmentPreviewsCount; 414 } 415 416 public Conversation() { 417 } 418 419 public static Conversation create(long id, Uri uri, String subject, long dateMs, String snippet, 420 boolean hasAttachment, Uri messageListUri, String senders, 421 int numMessages, int numDrafts, int sendingState, int priority, boolean read, 422 boolean seen, boolean starred, FolderList rawFolders, int convFlags, int personalLevel, 423 boolean spam, boolean phishing, boolean muted, Uri accountUri, 424 ConversationInfo conversationInfo, Uri conversationBase, boolean isRemote, 425 String attachmentPreviewUri0, String attachmentPreviewUri1, int attachmentPreviewStates, 426 int attachmentPreviewsCount) { 427 final Conversation conversation = new Conversation(); 428 429 conversation.id = id; 430 conversation.uri = uri; 431 conversation.subject = subject; 432 conversation.dateMs = dateMs; 433 conversation.snippet = snippet; 434 conversation.hasAttachments = hasAttachment; 435 conversation.messageListUri = messageListUri; 436 conversation.senders = emptyIfNull(senders); 437 conversation.numMessages = numMessages; 438 conversation.numDrafts = numDrafts; 439 conversation.sendingState = sendingState; 440 conversation.priority = priority; 441 conversation.read = read; 442 conversation.seen = seen; 443 conversation.starred = starred; 444 conversation.rawFolders = rawFolders; 445 conversation.convFlags = convFlags; 446 conversation.personalLevel = personalLevel; 447 conversation.spam = spam; 448 conversation.phishing = phishing; 449 conversation.muted = muted; 450 conversation.color = 0; 451 conversation.accountUri = accountUri; 452 conversation.conversationInfo = conversationInfo; 453 conversation.conversationBaseUri = conversationBase; 454 conversation.isRemote = isRemote; 455 conversation.attachmentPreviews = null; 456 conversation.attachmentPreviewUri0 = attachmentPreviewUri0; 457 conversation.attachmentPreviewUri1 = attachmentPreviewUri1; 458 conversation.attachmentPreviewStates = attachmentPreviewStates; 459 conversation.attachmentPreviewsCount = attachmentPreviewsCount; 460 return conversation; 461 } 462 463 private static final Bundle sConversationInfoRequest = new Bundle(1); 464 private static final Bundle sRawFoldersRequest = new Bundle(1); 465 466 private static ConversationInfo readConversationInfo(Cursor cursor) { 467 final ConversationInfo ci; 468 469 if (cursor instanceof ConversationCursor) { 470 final byte[] blob = ((ConversationCursor) cursor).getCachedBlob( 471 UIProvider.CONVERSATION_INFO_COLUMN); 472 if (blob != null && blob.length > 0) { 473 return ConversationInfo.fromBlob(blob); 474 } 475 } 476 477 final String key = UIProvider.ConversationCursorCommand.COMMAND_GET_CONVERSATION_INFO; 478 if (sConversationInfoRequest.isEmpty()) { 479 sConversationInfoRequest.putBoolean(key, true); 480 sConversationInfoRequest.putInt( 481 UIProvider.ConversationCursorCommand.COMMAND_KEY_OPTIONS, 482 UIProvider.ConversationCursorCommand.OPTION_MOVE_POSITION); 483 } 484 final Bundle response = cursor.respond(sConversationInfoRequest); 485 if (response.containsKey(key)) { 486 ci = response.getParcelable(key); 487 } else { 488 // legacy fallback 489 ci = ConversationInfo.fromBlob(cursor.getBlob(UIProvider.CONVERSATION_INFO_COLUMN)); 490 } 491 return ci; 492 } 493 494 private static FolderList readRawFolders(Cursor cursor) { 495 final FolderList fl; 496 497 if (cursor instanceof ConversationCursor) { 498 final byte[] blob = ((ConversationCursor) cursor).getCachedBlob( 499 UIProvider.CONVERSATION_RAW_FOLDERS_COLUMN); 500 if (blob != null && blob.length > 0) { 501 return FolderList.fromBlob(blob); 502 } 503 } 504 505 final String key = UIProvider.ConversationCursorCommand.COMMAND_GET_RAW_FOLDERS; 506 if (sRawFoldersRequest.isEmpty()) { 507 sRawFoldersRequest.putBoolean(key, true); 508 sRawFoldersRequest.putInt( 509 UIProvider.ConversationCursorCommand.COMMAND_KEY_OPTIONS, 510 UIProvider.ConversationCursorCommand.OPTION_MOVE_POSITION); 511 } 512 final Bundle response = cursor.respond(sRawFoldersRequest); 513 if (response.containsKey(key)) { 514 fl = response.getParcelable(key); 515 } else { 516 // legacy fallback 517 // TODO: delete this once Email supports the respond call 518 fl = FolderList.fromBlob( 519 cursor.getBlob(UIProvider.CONVERSATION_RAW_FOLDERS_COLUMN)); 520 } 521 return fl; 522 } 523 524 /** 525 * Apply any column values from the given {@link ContentValues} (where column names are the 526 * keys) to this conversation. 527 * 528 */ 529 public void applyCachedValues(ContentValues values) { 530 if (values == null) { 531 return; 532 } 533 for (String key : values.keySet()) { 534 final Object val = values.get(key); 535 LogUtils.i(LOG_TAG, "Conversation: applying cached value to col=%s val=%s", key, 536 val); 537 if (ConversationColumns.READ.equals(key)) { 538 read = (Integer) val != 0; 539 } else if (ConversationColumns.CONVERSATION_INFO.equals(key)) { 540 conversationInfo = ConversationInfo.fromBlob((byte[]) val); 541 } else if (ConversationColumns.FLAGS.equals(key)) { 542 convFlags = (Integer) val; 543 } else if (ConversationColumns.STARRED.equals(key)) { 544 starred = (Integer) val != 0; 545 } else if (ConversationColumns.SEEN.equals(key)) { 546 seen = (Integer) val != 0; 547 } else if (ConversationColumns.RAW_FOLDERS.equals(key)) { 548 rawFolders = FolderList.fromBlob((byte[]) val); 549 } else if (ConversationColumns.VIEWED.equals(key)) { 550 // ignore. this is not read from the cursor, either. 551 } else { 552 LogUtils.e(LOG_TAG, new UnsupportedOperationException(), 553 "unsupported cached conv value in col=%s", key); 554 } 555 } 556 } 557 558 /** 559 * Get the <strong>immutable</strong> list of {@link Folder}s for this conversation. To modify 560 * this list, make a new {@link FolderList} and use {@link #setRawFolders(FolderList)}. 561 * 562 * @return <strong>Immutable</strong> list of {@link Folder}s. 563 */ 564 public List<Folder> getRawFolders() { 565 return rawFolders.folders; 566 } 567 568 public void setRawFolders(FolderList folders) { 569 rawFolders = folders; 570 } 571 572 @Override 573 public boolean equals(Object o) { 574 if (o instanceof Conversation) { 575 Conversation conv = (Conversation) o; 576 return conv.uri.equals(uri); 577 } 578 return false; 579 } 580 581 @Override 582 public int hashCode() { 583 return uri.hashCode(); 584 } 585 586 /** 587 * Get if this conversation is marked as high priority. 588 */ 589 public boolean isImportant() { 590 return priority == UIProvider.ConversationPriority.IMPORTANT; 591 } 592 593 /** 594 * Get if this conversation is mostly dead 595 */ 596 public boolean isMostlyDead() { 597 return (convFlags & FLAG_MOSTLY_DEAD) != 0; 598 } 599 600 /** 601 * Returns true if the URI of the conversation specified as the needle was 602 * found in the collection of conversations specified as the haystack. False 603 * otherwise. This method is safe to call with null arguments. 604 * 605 * @param haystack 606 * @param needle 607 * @return true if the needle was found in the haystack, false otherwise. 608 */ 609 public final static boolean contains(Collection<Conversation> haystack, Conversation needle) { 610 // If the haystack is empty, it cannot contain anything. 611 if (haystack == null || haystack.size() <= 0) { 612 return false; 613 } 614 // The null folder exists everywhere. 615 if (needle == null) { 616 return true; 617 } 618 final long toFind = needle.id; 619 for (final Conversation c : haystack) { 620 if (toFind == c.id) { 621 return true; 622 } 623 } 624 return false; 625 } 626 627 /** 628 * Returns a collection of a single conversation. This method always returns 629 * a valid collection even if the input conversation is null. 630 * 631 * @param in a conversation, possibly null. 632 * @return a collection of the conversation. 633 */ 634 public static Collection<Conversation> listOf(Conversation in) { 635 final Collection<Conversation> target = (in == null) ? EMPTY : ImmutableList.of(in); 636 return target; 637 } 638 639 /** 640 * Get the snippet for this conversation. Masks that it may come from 641 * conversation info or the original deprecated snippet string. 642 */ 643 public String getSnippet() { 644 return conversationInfo != null && !TextUtils.isEmpty(conversationInfo.firstSnippet) ? 645 conversationInfo.firstSnippet : snippet; 646 } 647 648 /** 649 * Get the number of messages for this conversation. 650 */ 651 public int getNumMessages() { 652 return conversationInfo != null ? conversationInfo.messageCount : numMessages; 653 } 654 655 /** 656 * Get the number of drafts for this conversation. 657 */ 658 public int numDrafts() { 659 return conversationInfo != null ? conversationInfo.draftCount : numDrafts; 660 } 661 662 public boolean isViewed() { 663 return viewed; 664 } 665 666 public void markViewed() { 667 viewed = true; 668 } 669 670 public String getBaseUri(String defaultValue) { 671 return conversationBaseUri != null ? conversationBaseUri.toString() : defaultValue; 672 } 673 674 public ArrayList<String> getAttachmentPreviewUris() { 675 if (attachmentPreviews == null) { 676 attachmentPreviews = Lists.newArrayListWithCapacity(2); 677 if (!TextUtils.isEmpty(attachmentPreviewUri0)) { 678 attachmentPreviews.add(attachmentPreviewUri0); 679 } 680 if (!TextUtils.isEmpty(attachmentPreviewUri1)) { 681 attachmentPreviews.add(attachmentPreviewUri1); 682 } 683 } 684 return attachmentPreviews; 685 } 686 687 /** 688 * Create a human-readable string of all the conversations 689 * @param collection Any collection of conversations 690 * @return string with a human readable representation of the conversations. 691 */ 692 public static String toString(Collection<Conversation> collection) { 693 final StringBuilder out = new StringBuilder(collection.size() + " conversations:"); 694 int count = 0; 695 for (final Conversation c : collection) { 696 count++; 697 // Indent the conversations to make them easy to read in debug 698 // output. 699 out.append(" " + count + ": " + c.toString() + "\n"); 700 } 701 return out.toString(); 702 } 703 704 /** 705 * Returns an empty string if the specified string is null 706 */ 707 private static String emptyIfNull(String in) { 708 return in != null ? in : EMPTY_STRING; 709 } 710 711 /** 712 * Get the properly formatted subject and snippet string for display a 713 * conversation. 714 * 715 * @param context 716 * @param filteredSubject 717 * @param snippet 718 */ 719 public static String getSubjectAndSnippetForDisplay(Context context, 720 String filteredSubject, String snippet) { 721 if (sSubjectAndSnippet == null) { 722 sSubjectAndSnippet = context.getString(R.string.subject_and_snippet); 723 } 724 if (TextUtils.isEmpty(filteredSubject) && TextUtils.isEmpty(snippet)) { 725 return ""; 726 } else if (TextUtils.isEmpty(filteredSubject)) { 727 return snippet; 728 } else if (TextUtils.isEmpty(snippet)) { 729 return filteredSubject; 730 } 731 732 return String.format(sSubjectAndSnippet, filteredSubject, snippet); 733 } 734 735 /** 736 * Public object that knows how to construct Conversation given Cursors. This is not used by 737 * {@link ConversationCursor} or {@link ConversationCursorLoader}. 738 */ 739 public static final CursorCreator<Conversation> FACTORY = new CursorCreator<Conversation>() { 740 @Override 741 public Conversation createFromCursor(final Cursor c) { 742 return new Conversation(c); 743 } 744 745 @Override 746 public String toString() { 747 return "Conversation CursorCreator"; 748 } 749 }; 750 } 751