Conversation.java revision f8b613c1103952ae5cc1f8a5e6ae60aa62d73dec
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 if (cursor instanceof ConversationCursor) { 423 final byte[] blob = ((ConversationCursor) cursor).getCachedBlob( 424 UIProvider.CONVERSATION_INFO_COLUMN); 425 if (blob != null && blob.length > 0) { 426 return ConversationInfo.fromBlob(blob); 427 } 428 } 429 430 final String key = UIProvider.ConversationCursorCommand.COMMAND_GET_CONVERSATION_INFO; 431 if (sConversationInfoRequest.isEmpty()) { 432 sConversationInfoRequest.putBoolean(key, true); 433 sConversationInfoRequest.putInt( 434 UIProvider.ConversationCursorCommand.COMMAND_KEY_OPTIONS, 435 UIProvider.ConversationCursorCommand.OPTION_MOVE_POSITION); 436 } 437 final Bundle response = cursor.respond(sConversationInfoRequest); 438 if (response.containsKey(key)) { 439 ci = response.getParcelable(key); 440 } else { 441 // legacy fallback 442 ci = ConversationInfo.fromBlob(cursor.getBlob(UIProvider.CONVERSATION_INFO_COLUMN)); 443 } 444 return ci; 445 } 446 447 private static FolderList readRawFolders(Cursor cursor) { 448 final FolderList fl; 449 450 if (cursor instanceof ConversationCursor) { 451 final byte[] blob = ((ConversationCursor) cursor).getCachedBlob( 452 UIProvider.CONVERSATION_RAW_FOLDERS_COLUMN); 453 if (blob != null && blob.length > 0) { 454 return FolderList.fromBlob(blob); 455 } 456 } 457 458 final String key = UIProvider.ConversationCursorCommand.COMMAND_GET_RAW_FOLDERS; 459 if (sRawFoldersRequest.isEmpty()) { 460 sRawFoldersRequest.putBoolean(key, true); 461 sRawFoldersRequest.putInt( 462 UIProvider.ConversationCursorCommand.COMMAND_KEY_OPTIONS, 463 UIProvider.ConversationCursorCommand.OPTION_MOVE_POSITION); 464 } 465 final Bundle response = cursor.respond(sRawFoldersRequest); 466 if (response.containsKey(key)) { 467 fl = response.getParcelable(key); 468 } else { 469 // legacy fallback 470 fl = FolderList.fromBlob( 471 cursor.getBlob(UIProvider.CONVERSATION_RAW_FOLDERS_COLUMN)); 472 } 473 return fl; 474 } 475 476 /** 477 * Apply any column values from the given {@link ContentValues} (where column names are the 478 * keys) to this conversation. 479 * 480 */ 481 public void applyCachedValues(ContentValues values) { 482 if (values == null) { 483 return; 484 } 485 for (String key : values.keySet()) { 486 final Object val = values.get(key); 487 LogUtils.i(LOG_TAG, "Conversation: applying cached value to col=%s val=%s", key, 488 val); 489 if (ConversationColumns.READ.equals(key)) { 490 read = (Integer) val != 0; 491 } else if (ConversationColumns.CONVERSATION_INFO.equals(key)) { 492 conversationInfo = ConversationInfo.fromBlob((byte[]) val); 493 } else if (ConversationColumns.FLAGS.equals(key)) { 494 convFlags = (Integer) val; 495 } else if (ConversationColumns.STARRED.equals(key)) { 496 starred = (Integer) val != 0; 497 } else if (ConversationColumns.SEEN.equals(key)) { 498 seen = (Integer) val != 0; 499 } else if (ConversationColumns.RAW_FOLDERS.equals(key)) { 500 rawFolders = FolderList.fromBlob((byte[]) val); 501 } else if (ConversationColumns.VIEWED.equals(key)) { 502 // ignore. this is not read from the cursor, either. 503 } else { 504 LogUtils.e(LOG_TAG, new UnsupportedOperationException(), 505 "unsupported cached conv value in col=%s", key); 506 } 507 } 508 } 509 510 /** 511 * Get the <strong>immutable</strong> list of {@link Folder}s for this conversation. To modify 512 * this list, make a new {@link FolderList} and use {@link #setRawFolders(FolderList)}. 513 * 514 * @return <strong>Immutable</strong> list of {@link Folder}s. 515 */ 516 public List<Folder> getRawFolders() { 517 return rawFolders.folders; 518 } 519 520 public void setRawFolders(FolderList folders) { 521 clearCachedFolders(); 522 rawFolders = folders; 523 } 524 525 private void clearCachedFolders() { 526 cachedDisplayableFolders = null; 527 } 528 529 public ArrayList<Folder> getRawFoldersForDisplay(final Uri ignoreFolderUri, 530 final int ignoreFolderType) { 531 if (cachedDisplayableFolders == null) { 532 cachedDisplayableFolders = new ArrayList<Folder>(); 533 for (Folder folder : rawFolders.folders) { 534 // skip the ignoreFolder 535 if (ignoreFolderUri != null && ignoreFolderUri.equals(folder.uri)) { 536 continue; 537 } 538 // Skip the ignoreFolderType 539 if (ignoreFolderType >= 0 && folder.isType(ignoreFolderType)) { 540 continue; 541 } 542 cachedDisplayableFolders.add(folder); 543 } 544 } 545 return cachedDisplayableFolders; 546 } 547 548 @Override 549 public boolean equals(Object o) { 550 if (o instanceof Conversation) { 551 Conversation conv = (Conversation) o; 552 return conv.uri.equals(uri); 553 } 554 return false; 555 } 556 557 @Override 558 public int hashCode() { 559 return uri.hashCode(); 560 } 561 562 /** 563 * Get if this conversation is marked as high priority. 564 */ 565 public boolean isImportant() { 566 return priority == UIProvider.ConversationPriority.IMPORTANT; 567 } 568 569 /** 570 * Get if this conversation is mostly dead 571 */ 572 public boolean isMostlyDead() { 573 return (convFlags & FLAG_MOSTLY_DEAD) != 0; 574 } 575 576 /** 577 * Returns true if the URI of the conversation specified as the needle was 578 * found in the collection of conversations specified as the haystack. False 579 * otherwise. This method is safe to call with null arguments. 580 * 581 * @param haystack 582 * @param needle 583 * @return true if the needle was found in the haystack, false otherwise. 584 */ 585 public final static boolean contains(Collection<Conversation> haystack, Conversation needle) { 586 // If the haystack is empty, it cannot contain anything. 587 if (haystack == null || haystack.size() <= 0) { 588 return false; 589 } 590 // The null folder exists everywhere. 591 if (needle == null) { 592 return true; 593 } 594 final long toFind = needle.id; 595 for (final Conversation c : haystack) { 596 if (toFind == c.id) { 597 return true; 598 } 599 } 600 return false; 601 } 602 603 /** 604 * Returns a collection of a single conversation. This method always returns 605 * a valid collection even if the input conversation is null. 606 * 607 * @param in a conversation, possibly null. 608 * @return a collection of the conversation. 609 */ 610 public static Collection<Conversation> listOf(Conversation in) { 611 final Collection<Conversation> target = (in == null) ? EMPTY : ImmutableList.of(in); 612 return target; 613 } 614 615 /** 616 * Get the snippet for this conversation. Masks that it may come from 617 * conversation info or the original deprecated snippet string. 618 */ 619 public String getSnippet() { 620 return conversationInfo != null && !TextUtils.isEmpty(conversationInfo.firstSnippet) ? 621 conversationInfo.firstSnippet : snippet; 622 } 623 624 /** 625 * Get the number of messages for this conversation. 626 */ 627 public int getNumMessages() { 628 return conversationInfo != null ? conversationInfo.messageCount : numMessages; 629 } 630 631 /** 632 * Get the number of drafts for this conversation. 633 */ 634 public int numDrafts() { 635 return conversationInfo != null ? conversationInfo.draftCount : numDrafts; 636 } 637 638 public boolean isViewed() { 639 return viewed; 640 } 641 642 public void markViewed() { 643 viewed = true; 644 } 645 646 public String getBaseUri(String defaultValue) { 647 return conversationBaseUri != null ? conversationBaseUri.toString() : defaultValue; 648 } 649 650 public int getAttachmentsCount() { 651 return getAttachments().size(); 652 } 653 654 public ArrayList<String> getAttachments() { 655 return Lists.newArrayList(); 656 } 657 658 /** 659 * Create a human-readable string of all the conversations 660 * @param collection Any collection of conversations 661 * @return string with a human readable representation of the conversations. 662 */ 663 public static String toString(Collection<Conversation> collection) { 664 final StringBuilder out = new StringBuilder(collection.size() + " conversations:"); 665 int count = 0; 666 for (final Conversation c : collection) { 667 count++; 668 // Indent the conversations to make them easy to read in debug 669 // output. 670 out.append(" " + count + ": " + c.toString() + "\n"); 671 } 672 return out.toString(); 673 } 674 675 /** 676 * Returns an empty string if the specified string is null 677 */ 678 private static String emptyIfNull(String in) { 679 return in != null ? in : EMPTY_STRING; 680 } 681 682 /** 683 * Get the properly formatted subject and snippet string for display a 684 * conversation. 685 * 686 * @param context 687 * @param filteredSubject 688 * @param snippet 689 */ 690 public static String getSubjectAndSnippetForDisplay(Context context, 691 String filteredSubject, String snippet) { 692 if (sSubjectAndSnippet == null) { 693 sSubjectAndSnippet = context.getString(R.string.subject_and_snippet); 694 } 695 if (TextUtils.isEmpty(filteredSubject) && TextUtils.isEmpty(snippet)) { 696 return ""; 697 } else if (TextUtils.isEmpty(filteredSubject)) { 698 return snippet; 699 } else if (TextUtils.isEmpty(snippet)) { 700 return filteredSubject; 701 } 702 703 return String.format(sSubjectAndSnippet, filteredSubject, snippet); 704 } 705 706 /** 707 * Public object that knows how to construct Conversation given Cursors. This is not used by 708 * {@link ConversationCursor} or {@link ConversationCursorLoader}. 709 */ 710 public static final CursorCreator<Conversation> FACTORY = new CursorCreator<Conversation>() { 711 @Override 712 public Conversation createFromCursor(final Cursor c) { 713 return new Conversation(c); 714 } 715 716 @Override 717 public String toString() { 718 return "Conversation CursorCreator"; 719 } 720 }; 721} 722