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