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