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