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