Conversation.java revision bf9508d3877d05742b2f5d23e1780366d3e1aa2e
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.Context; 20import android.database.Cursor; 21import android.net.Uri; 22import android.os.Parcel; 23import android.os.Parcelable; 24import android.provider.BaseColumns; 25import android.text.SpannableStringBuilder; 26import android.text.TextUtils; 27 28import com.android.mail.R; 29import com.android.mail.providers.UIProvider.ConversationColumns; 30import com.google.common.collect.ImmutableList; 31 32import java.util.ArrayList; 33import java.util.Collection; 34import java.util.Collections; 35 36public class Conversation implements Parcelable { 37 public static final int NO_POSITION = -1; 38 39 private static final String EMPTY_STRING = ""; 40 41 /** 42 * @see BaseColumns#_ID 43 */ 44 public long id; 45 /** 46 * @see UIProvider.ConversationColumns#URI 47 */ 48 public Uri uri; 49 /** 50 * @see UIProvider.ConversationColumns#SUBJECT 51 */ 52 public String subject; 53 /** 54 * @see UIProvider.ConversationColumns#DATE_RECEIVED_MS 55 */ 56 public long dateMs; 57 /** 58 * @see UIProvider.ConversationColumns#SNIPPET 59 */ 60 @Deprecated 61 public String snippet; 62 /** 63 * @see UIProvider.ConversationColumns#HAS_ATTACHMENTS 64 */ 65 public boolean hasAttachments; 66 /** 67 * @see UIProvider.ConversationColumns#MESSAGE_LIST_URI 68 */ 69 public Uri messageListUri; 70 /** 71 * @see UIProvider.ConversationColumns#SENDER_INFO 72 */ 73 @Deprecated 74 public String senders; 75 /** 76 * @see UIProvider.ConversationColumns#NUM_MESSAGES 77 */ 78 private int numMessages; 79 /** 80 * @see UIProvider.ConversationColumns#NUM_DRAFTS 81 */ 82 private int numDrafts; 83 /** 84 * @see UIProvider.ConversationColumns#SENDING_STATE 85 */ 86 public int sendingState; 87 /** 88 * @see UIProvider.ConversationColumns#PRIORITY 89 */ 90 public int priority; 91 /** 92 * @see UIProvider.ConversationColumns#READ 93 */ 94 public boolean read; 95 /** 96 * @see UIProvider.ConversationColumns#STARRED 97 */ 98 public boolean starred; 99 /** 100 * @see UIProvider.ConversationColumns#RAW_FOLDERS 101 */ 102 private String rawFolders; 103 /** 104 * @see UIProvider.ConversationColumns#FLAGS 105 */ 106 public int convFlags; 107 /** 108 * @see UIProvider.ConversationColumns#PERSONAL_LEVEL 109 */ 110 public int personalLevel; 111 /** 112 * @see UIProvider.ConversationColumns#SPAM 113 */ 114 public boolean spam; 115 /** 116 * @see UIProvider.ConversationColumns#MUTED 117 */ 118 public boolean muted; 119 /** 120 * @see UIProvider.ConversationColumns#PHISHING 121 */ 122 public boolean phishing; 123 /** 124 * @see UIProvider.ConversationColumns#COLOR 125 */ 126 public int color; 127 /** 128 * @see UIProvider.ConversationColumns#ACCOUNT_URI 129 */ 130 public Uri accountUri; 131 /** 132 * @see UIProvider.ConversationColumns#CONVERSATION_INFO 133 */ 134 public ConversationInfo conversationInfo; 135 /** 136 * @see UIProvider.ConversationColumns#CONVERSATION_BASE_URI 137 */ 138 public Uri conversationBaseUri; 139 /** 140 * @see UIProvider.ConversationColumns#CONVERSATION_COOKIE 141 */ 142 public String conversationCookie; 143 /** 144 * @see UIProvider.ConversationColumns#REMOTE 145 */ 146 public boolean isRemote; 147 148 // Used within the UI to indicate the adapter position of this conversation 149 public transient int position; 150 // Used within the UI to indicate that a Conversation should be removed from 151 // the ConversationCursor when executing an update, e.g. the the 152 // Conversation is no longer in the ConversationList for the current folder, 153 // that is it's now in some other folder(s) 154 public transient boolean localDeleteOnUpdate; 155 156 private ArrayList<Folder> cachedRawFolders; 157 private ArrayList<Folder> cachedDisplayableFolders; 158 159 // Constituents of convFlags below 160 // Flag indicating that the item has been deleted, but will continue being 161 // shown in the list Delete/Archive of a mostly-dead item will NOT propagate 162 // the delete/archive, but WILL remove the item from the cursor 163 public static final int FLAG_MOSTLY_DEAD = 1 << 0; 164 165 /** An immutable, empty conversation list */ 166 public static final Collection<Conversation> EMPTY = Collections.emptyList(); 167 168 @Override 169 public int describeContents() { 170 return 0; 171 } 172 173 @Override 174 public void writeToParcel(Parcel dest, int flags) { 175 dest.writeLong(id); 176 dest.writeParcelable(uri, flags); 177 dest.writeString(subject); 178 dest.writeLong(dateMs); 179 dest.writeString(snippet); 180 dest.writeInt(hasAttachments ? 1 : 0); 181 dest.writeParcelable(messageListUri, 0); 182 dest.writeString(senders); 183 dest.writeInt(numMessages); 184 dest.writeInt(numDrafts); 185 dest.writeInt(sendingState); 186 dest.writeInt(priority); 187 dest.writeInt(read ? 1 : 0); 188 dest.writeInt(starred ? 1 : 0); 189 dest.writeString(rawFolders); 190 dest.writeInt(convFlags); 191 dest.writeInt(personalLevel); 192 dest.writeInt(spam ? 1 : 0); 193 dest.writeInt(phishing ? 1 : 0); 194 dest.writeInt(muted ? 1 : 0); 195 dest.writeInt(color); 196 dest.writeParcelable(accountUri, 0); 197 dest.writeString(ConversationInfo.toString(conversationInfo)); 198 dest.writeParcelable(conversationBaseUri, 0); 199 dest.writeString(conversationCookie); 200 dest.writeInt(isRemote ? 1 : 0); 201 } 202 203 private Conversation(Parcel in) { 204 id = in.readLong(); 205 uri = in.readParcelable(null); 206 subject = in.readString(); 207 dateMs = in.readLong(); 208 snippet = in.readString(); 209 hasAttachments = (in.readInt() != 0); 210 messageListUri = in.readParcelable(null); 211 senders = emptyIfNull(in.readString()); 212 numMessages = in.readInt(); 213 numDrafts = in.readInt(); 214 sendingState = in.readInt(); 215 priority = in.readInt(); 216 read = (in.readInt() != 0); 217 starred = (in.readInt() != 0); 218 rawFolders = in.readString(); 219 convFlags = in.readInt(); 220 personalLevel = in.readInt(); 221 spam = in.readInt() != 0; 222 phishing = in.readInt() != 0; 223 muted = in.readInt() != 0; 224 color = in.readInt(); 225 accountUri = in.readParcelable(null); 226 position = NO_POSITION; 227 localDeleteOnUpdate = false; 228 conversationInfo = ConversationInfo.fromString(in.readString()); 229 conversationBaseUri = in.readParcelable(null); 230 conversationCookie = in.readString(); 231 isRemote = in.readInt() != 0; 232 } 233 234 @Override 235 public String toString() { 236 return "[conversation id=" + id + ", subject =" + subject + "]"; 237 } 238 239 public static final Creator<Conversation> CREATOR = new Creator<Conversation>() { 240 241 @Override 242 public Conversation createFromParcel(Parcel source) { 243 return new Conversation(source); 244 } 245 246 @Override 247 public Conversation[] newArray(int size) { 248 return new Conversation[size]; 249 } 250 251 }; 252 253 public static final Uri MOVE_CONVERSATIONS_URI = Uri.parse("content://moveconversations"); 254 255 /** 256 * The column that needs to be updated to change the read state of a 257 * conversation. 258 */ 259 public static final String UPDATE_FOLDER_COLUMN = ConversationColumns.RAW_FOLDERS; 260 261 public Conversation(Cursor cursor) { 262 if (cursor != null) { 263 id = cursor.getLong(UIProvider.CONVERSATION_ID_COLUMN); 264 uri = Uri.parse(cursor.getString(UIProvider.CONVERSATION_URI_COLUMN)); 265 dateMs = cursor.getLong(UIProvider.CONVERSATION_DATE_RECEIVED_MS_COLUMN); 266 subject = cursor.getString(UIProvider.CONVERSATION_SUBJECT_COLUMN); 267 // Don't allow null subject 268 if (subject == null) { 269 subject = ""; 270 } 271 hasAttachments = cursor.getInt(UIProvider.CONVERSATION_HAS_ATTACHMENTS_COLUMN) != 0; 272 String messageList = cursor.getString(UIProvider.CONVERSATION_MESSAGE_LIST_URI_COLUMN); 273 messageListUri = !TextUtils.isEmpty(messageList) ? Uri.parse(messageList) : null; 274 sendingState = cursor.getInt(UIProvider.CONVERSATION_SENDING_STATE_COLUMN); 275 priority = cursor.getInt(UIProvider.CONVERSATION_PRIORITY_COLUMN); 276 read = cursor.getInt(UIProvider.CONVERSATION_READ_COLUMN) != 0; 277 starred = cursor.getInt(UIProvider.CONVERSATION_STARRED_COLUMN) != 0; 278 rawFolders = cursor.getString(UIProvider.CONVERSATION_RAW_FOLDERS_COLUMN); 279 convFlags = cursor.getInt(UIProvider.CONVERSATION_FLAGS_COLUMN); 280 personalLevel = cursor.getInt(UIProvider.CONVERSATION_PERSONAL_LEVEL_COLUMN); 281 spam = cursor.getInt(UIProvider.CONVERSATION_IS_SPAM_COLUMN) != 0; 282 phishing = cursor.getInt(UIProvider.CONVERSATION_IS_PHISHING_COLUMN) != 0; 283 muted = cursor.getInt(UIProvider.CONVERSATION_MUTED_COLUMN) != 0; 284 color = cursor.getInt(UIProvider.CONVERSATION_COLOR_COLUMN); 285 String account = cursor.getString(UIProvider.CONVERSATION_ACCOUNT_URI_COLUMN); 286 accountUri = !TextUtils.isEmpty(account) ? Uri.parse(account) : null; 287 position = NO_POSITION; 288 localDeleteOnUpdate = false; 289 conversationInfo = ConversationInfo.fromString(cursor 290 .getString(UIProvider.CONVERSATION_INFO_COLUMN)); 291 final String conversationBase = 292 cursor.getString(UIProvider.CONVERSATION_BASE_URI_COLUMN); 293 conversationBaseUri = !TextUtils.isEmpty(conversationBase) ? 294 Uri.parse(conversationBase) : null; 295 conversationCookie = cursor.getString(UIProvider.CONVERSATION_COOKIE_COLUMN); 296 if (conversationInfo == null) { 297 snippet = cursor.getString(UIProvider.CONVERSATION_SNIPPET_COLUMN); 298 senders = emptyIfNull(cursor.getString(UIProvider.CONVERSATION_SENDER_INFO_COLUMN)); 299 numMessages = cursor.getInt(UIProvider.CONVERSATION_NUM_MESSAGES_COLUMN); 300 numDrafts = cursor.getInt(UIProvider.CONVERSATION_NUM_DRAFTS_COLUMN); 301 } 302 isRemote = cursor.getInt(UIProvider.CONVERSATION_REMOTE_COLUMN) != 0; 303 } 304 } 305 306 public Conversation() { 307 } 308 309 public static Conversation create(long id, Uri uri, String subject, long dateMs, 310 String snippet, boolean hasAttachment, Uri messageListUri, String senders, 311 int numMessages, int numDrafts, int sendingState, int priority, boolean read, 312 boolean starred, String rawFolders, int convFlags, int personalLevel, boolean spam, 313 boolean phishing, boolean muted, Uri accountUri, ConversationInfo conversationInfo, 314 Uri conversationBase, String conversationCookie, boolean isRemote) { 315 316 final Conversation conversation = new Conversation(); 317 318 conversation.id = id; 319 conversation.uri = uri; 320 conversation.subject = subject; 321 conversation.dateMs = dateMs; 322 conversation.snippet = snippet; 323 conversation.hasAttachments = hasAttachment; 324 conversation.messageListUri = messageListUri; 325 conversation.senders = emptyIfNull(senders); 326 conversation.numMessages = numMessages; 327 conversation.numDrafts = numDrafts; 328 conversation.sendingState = sendingState; 329 conversation.priority = priority; 330 conversation.read = read; 331 conversation.starred = starred; 332 conversation.rawFolders = rawFolders; 333 conversation.convFlags = convFlags; 334 conversation.personalLevel = personalLevel; 335 conversation.spam = spam; 336 conversation.phishing = phishing; 337 conversation.muted = muted; 338 conversation.color = 0; 339 conversation.accountUri = accountUri; 340 conversation.conversationInfo = conversationInfo; 341 conversation.conversationBaseUri = conversationBase; 342 conversation.conversationCookie = conversationCookie; 343 conversation.isRemote = isRemote; 344 return conversation; 345 } 346 347 public ArrayList<Folder> getRawFolders() { 348 if (cachedRawFolders == null) { 349 // Create cached folders. 350 if (!TextUtils.isEmpty(rawFolders)) { 351 cachedRawFolders = Folder.getFoldersArray(rawFolders); 352 } else { 353 return new ArrayList<Folder>(); 354 } 355 } 356 return cachedRawFolders; 357 } 358 359 public void setRawFolders(String raw) { 360 clearCachedFolders(); 361 rawFolders = raw; 362 } 363 364 public String getRawFoldersString() { 365 return rawFolders; 366 } 367 368 private void clearCachedFolders() { 369 cachedRawFolders = null; 370 cachedDisplayableFolders = null; 371 } 372 373 public ArrayList<Folder> getRawFoldersForDisplay(Folder ignoreFolder) { 374 ArrayList<Folder> folders = getRawFolders(); 375 if (cachedDisplayableFolders == null) { 376 cachedDisplayableFolders = new ArrayList<Folder>(); 377 for (Folder folder : folders) { 378 // skip the ignoreFolder 379 if (ignoreFolder != null && ignoreFolder.equals(folder)) { 380 continue; 381 } 382 cachedDisplayableFolders.add(folder); 383 } 384 } 385 return cachedDisplayableFolders; 386 } 387 388 @Override 389 public boolean equals(Object o) { 390 if (o instanceof Conversation) { 391 Conversation conv = (Conversation) o; 392 return conv.uri.equals(uri); 393 } 394 return false; 395 } 396 397 @Override 398 public int hashCode() { 399 return uri.hashCode(); 400 } 401 402 /** 403 * Get if this conversation is marked as high priority. 404 */ 405 public boolean isImportant() { 406 return priority == UIProvider.ConversationPriority.IMPORTANT; 407 } 408 409 /** 410 * Get if this conversation is mostly dead 411 */ 412 public boolean isMostlyDead() { 413 return (convFlags & FLAG_MOSTLY_DEAD) != 0; 414 } 415 416 /** 417 * Returns true if the URI of the conversation specified as the needle was 418 * found in the collection of conversations specified as the haystack. False 419 * otherwise. This method is safe to call with null arguments. 420 * 421 * @param haystack 422 * @param needle 423 * @return true if the needle was found in the haystack, false otherwise. 424 */ 425 public final static boolean contains(Collection<Conversation> haystack, Conversation needle) { 426 // If the haystack is empty, it cannot contain anything. 427 if (haystack == null || haystack.size() <= 0) { 428 return false; 429 } 430 // The null folder exists everywhere. 431 if (needle == null) { 432 return true; 433 } 434 final long toFind = needle.id; 435 for (final Conversation c : haystack) { 436 if (toFind == c.id) { 437 return true; 438 } 439 } 440 return false; 441 } 442 443 /** 444 * Returns a collection of a single conversation. This method always returns 445 * a valid collection even if the input conversation is null. 446 * 447 * @param in a conversation, possibly null. 448 * @return a collection of the conversation. 449 */ 450 public static Collection<Conversation> listOf(Conversation in) { 451 final Collection<Conversation> target = (in == null) ? EMPTY : ImmutableList.of(in); 452 return target; 453 } 454 455 /** 456 * Get the snippet for this conversation. Masks that it may come from 457 * conversation info or the original deprecated snippet string. 458 */ 459 public String getSnippet() { 460 return conversationInfo != null && !TextUtils.isEmpty(conversationInfo.firstSnippet) ? 461 conversationInfo.firstSnippet : snippet; 462 } 463 464 /** 465 * Get the number of messages for this conversation. 466 */ 467 public int getNumMessages() { 468 return conversationInfo != null ? conversationInfo.messageCount : numMessages; 469 } 470 471 /** 472 * Get the number of drafts for this conversation. 473 */ 474 public int numDrafts() { 475 return conversationInfo != null ? conversationInfo.draftCount : numDrafts; 476 } 477 478 /** 479 * Create a human-readable string of all the conversations 480 * @param collection Any collection of conversations 481 * @return string with a human readable representation of the conversations. 482 */ 483 public static String toString(Collection<Conversation> collection) { 484 final StringBuilder out = new StringBuilder(collection.size() + " conversations:"); 485 int count = 0; 486 for (final Conversation c : collection) { 487 count++; 488 // Indent the conversations to make them easy to read in debug 489 // output. 490 out.append(" " + count + ": " + c.toString() + "\n"); 491 } 492 return out.toString(); 493 } 494 495 /** 496 * Returns an empty string if the specified string is null 497 */ 498 private static String emptyIfNull(String in) { 499 return in != null ? in : EMPTY_STRING; 500 } 501 502 /** 503 * Get the properly formatted subject and snippet string for display a conversation. 504 */ 505 public static SpannableStringBuilder getSubjectAndSnippetForDisplay(Context context, 506 String filteredSubject, String snippet) { 507 return new SpannableStringBuilder((!TextUtils.isEmpty(snippet)) ? 508 context.getString(R.string.subject_and_snippet, filteredSubject, snippet) 509 : filteredSubject); 510 } 511} 512