Conversation.java revision 820f051b37b45b3e3729bda5c4302a879ee6aa4d
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 // Used within the UI to indicate the adapter position of this conversation 134 public transient int position; 135 // Used within the UI to indicate that a Conversation should be removed from 136 // the ConversationCursor when executing an update, e.g. the the 137 // Conversation is no longer in the ConversationList for the current folder, 138 // that is it's now in some other folder(s) 139 public transient boolean localDeleteOnUpdate; 140 141 private ArrayList<Folder> cachedRawFolders; 142 private ArrayList<Folder> cachedDisplayableFolders; 143 144 // Constituents of convFlags below 145 // Flag indicating that the item has been deleted, but will continue being 146 // shown in the list Delete/Archive of a mostly-dead item will NOT propagate 147 // the delete/archive, but WILL remove the item from the cursor 148 public static final int FLAG_MOSTLY_DEAD = 1 << 0; 149 150 /** An immutable, empty conversation list */ 151 public static final Collection<Conversation> EMPTY = Collections.emptyList(); 152 153 @Override 154 public int describeContents() { 155 return 0; 156 } 157 158 @Override 159 public void writeToParcel(Parcel dest, int flags) { 160 dest.writeLong(id); 161 dest.writeParcelable(uri, flags); 162 dest.writeString(subject); 163 dest.writeLong(dateMs); 164 dest.writeString(snippet); 165 dest.writeByte(hasAttachments ? (byte) 1 : 0); 166 dest.writeParcelable(messageListUri, 0); 167 dest.writeString(senders); 168 dest.writeInt(numMessages); 169 dest.writeInt(numDrafts); 170 dest.writeInt(sendingState); 171 dest.writeInt(priority); 172 dest.writeByte(read ? (byte) 1 : 0); 173 dest.writeByte(starred ? (byte) 1 : 0); 174 dest.writeString(rawFolders); 175 dest.writeInt(convFlags); 176 dest.writeInt(personalLevel); 177 dest.writeInt(spam ? 1 : 0); 178 dest.writeInt(phishing ? 1 : 0); 179 dest.writeInt(muted ? 1 : 0); 180 dest.writeInt(color); 181 dest.writeParcelable(accountUri, 0); 182 dest.writeString(ConversationInfo.toString(conversationInfo)); 183 } 184 185 private Conversation(Parcel in) { 186 id = in.readLong(); 187 uri = in.readParcelable(null); 188 subject = in.readString(); 189 dateMs = in.readLong(); 190 snippet = in.readString(); 191 hasAttachments = (in.readByte() != 0); 192 messageListUri = in.readParcelable(null); 193 senders = emptyIfNull(in.readString()); 194 numMessages = in.readInt(); 195 numDrafts = in.readInt(); 196 sendingState = in.readInt(); 197 priority = in.readInt(); 198 read = (in.readByte() != 0); 199 starred = (in.readByte() != 0); 200 rawFolders = in.readString(); 201 convFlags = in.readInt(); 202 personalLevel = in.readInt(); 203 spam = in.readInt() != 0; 204 phishing = in.readInt() != 0; 205 muted = in.readInt() != 0; 206 color = in.readInt(); 207 accountUri = in.readParcelable(null); 208 position = NO_POSITION; 209 localDeleteOnUpdate = false; 210 conversationInfo = ConversationInfo.fromString(in.readString()); 211 } 212 213 @Override 214 public String toString() { 215 return "[conversation id=" + id + ", subject =" + subject + "]"; 216 } 217 218 public static final Creator<Conversation> CREATOR = new Creator<Conversation>() { 219 220 @Override 221 public Conversation createFromParcel(Parcel source) { 222 return new Conversation(source); 223 } 224 225 @Override 226 public Conversation[] newArray(int size) { 227 return new Conversation[size]; 228 } 229 230 }; 231 232 public static final Uri MOVE_CONVERSATIONS_URI = Uri.parse("content://moveconversations"); 233 234 /** 235 * The column that needs to be updated to change the read state of a 236 * conversation. 237 */ 238 public static final String UPDATE_FOLDER_COLUMN = ConversationColumns.RAW_FOLDERS; 239 240 public Conversation(Cursor cursor) { 241 if (cursor != null) { 242 id = cursor.getLong(UIProvider.CONVERSATION_ID_COLUMN); 243 uri = Uri.parse(cursor.getString(UIProvider.CONVERSATION_URI_COLUMN)); 244 dateMs = cursor.getLong(UIProvider.CONVERSATION_DATE_RECEIVED_MS_COLUMN); 245 subject = cursor.getString(UIProvider.CONVERSATION_SUBJECT_COLUMN); 246 // Don't allow null subject 247 if (subject == null) { 248 subject = ""; 249 } 250 hasAttachments = cursor.getInt(UIProvider.CONVERSATION_HAS_ATTACHMENTS_COLUMN) != 0; 251 String messageList = cursor.getString(UIProvider.CONVERSATION_MESSAGE_LIST_URI_COLUMN); 252 messageListUri = !TextUtils.isEmpty(messageList) ? Uri.parse(messageList) : null; 253 sendingState = cursor.getInt(UIProvider.CONVERSATION_SENDING_STATE_COLUMN); 254 priority = cursor.getInt(UIProvider.CONVERSATION_PRIORITY_COLUMN); 255 read = cursor.getInt(UIProvider.CONVERSATION_READ_COLUMN) != 0; 256 starred = cursor.getInt(UIProvider.CONVERSATION_STARRED_COLUMN) != 0; 257 rawFolders = cursor.getString(UIProvider.CONVERSATION_RAW_FOLDERS_COLUMN); 258 convFlags = cursor.getInt(UIProvider.CONVERSATION_FLAGS_COLUMN); 259 personalLevel = cursor.getInt(UIProvider.CONVERSATION_PERSONAL_LEVEL_COLUMN); 260 spam = cursor.getInt(UIProvider.CONVERSATION_IS_SPAM_COLUMN) != 0; 261 phishing = cursor.getInt(UIProvider.CONVERSATION_IS_PHISHING_COLUMN) != 0; 262 muted = cursor.getInt(UIProvider.CONVERSATION_MUTED_COLUMN) != 0; 263 color = cursor.getInt(UIProvider.CONVERSATION_COLOR_COLUMN); 264 String account = cursor.getString(UIProvider.CONVERSATION_ACCOUNT_URI_COLUMN); 265 accountUri = !TextUtils.isEmpty(account) ? Uri.parse(account) : null; 266 position = NO_POSITION; 267 localDeleteOnUpdate = false; 268 conversationInfo = ConversationInfo.fromString(cursor 269 .getString(UIProvider.CONVERSATION_INFO_COLUMN)); 270 if (conversationInfo == null) { 271 snippet = cursor.getString(UIProvider.CONVERSATION_SNIPPET_COLUMN); 272 senders = emptyIfNull(cursor.getString(UIProvider.CONVERSATION_SENDER_INFO_COLUMN)); 273 numMessages = cursor.getInt(UIProvider.CONVERSATION_NUM_MESSAGES_COLUMN); 274 numDrafts = cursor.getInt(UIProvider.CONVERSATION_NUM_DRAFTS_COLUMN); 275 } 276 } 277 } 278 279 public Conversation() { 280 } 281 282 public static Conversation create(long id, Uri uri, String subject, long dateMs, 283 String snippet, boolean hasAttachment, Uri messageListUri, String senders, 284 int numMessages, int numDrafts, int sendingState, int priority, boolean read, 285 boolean starred, String rawFolders, int convFlags, int personalLevel, boolean spam, 286 boolean phishing, boolean muted, Uri accountUri, ConversationInfo conversationInfo) { 287 288 final Conversation conversation = new Conversation(); 289 290 conversation.id = id; 291 conversation.uri = uri; 292 conversation.subject = subject; 293 conversation.dateMs = dateMs; 294 conversation.snippet = snippet; 295 conversation.hasAttachments = hasAttachment; 296 conversation.messageListUri = messageListUri; 297 conversation.senders = emptyIfNull(senders); 298 conversation.numMessages = numMessages; 299 conversation.numDrafts = numDrafts; 300 conversation.sendingState = sendingState; 301 conversation.priority = priority; 302 conversation.read = read; 303 conversation.starred = starred; 304 conversation.rawFolders = rawFolders; 305 conversation.convFlags = convFlags; 306 conversation.personalLevel = personalLevel; 307 conversation.spam = spam; 308 conversation.phishing = phishing; 309 conversation.muted = muted; 310 conversation.color = 0; 311 conversation.accountUri = accountUri; 312 conversation.conversationInfo = conversationInfo; 313 return conversation; 314 } 315 316 public ArrayList<Folder> getRawFolders() { 317 if (cachedRawFolders == null) { 318 // Create cached folders. 319 if (!TextUtils.isEmpty(rawFolders)) { 320 cachedRawFolders = Folder.getFoldersArray(rawFolders); 321 } else { 322 return new ArrayList<Folder>(); 323 } 324 } 325 return cachedRawFolders; 326 } 327 328 public void setRawFolders(String raw) { 329 clearCachedFolders(); 330 rawFolders = raw; 331 } 332 333 public String getRawFoldersString() { 334 return rawFolders; 335 } 336 337 private void clearCachedFolders() { 338 cachedRawFolders = null; 339 cachedDisplayableFolders = null; 340 } 341 342 public ArrayList<Folder> getRawFoldersForDisplay(Folder ignoreFolder) { 343 ArrayList<Folder> folders = getRawFolders(); 344 if (cachedDisplayableFolders == null) { 345 cachedDisplayableFolders = new ArrayList<Folder>(); 346 for (Folder folder : folders) { 347 // skip the ignoreFolder 348 if (ignoreFolder != null && ignoreFolder.equals(folder)) { 349 continue; 350 } 351 cachedDisplayableFolders.add(folder); 352 } 353 } 354 return cachedDisplayableFolders; 355 } 356 357 @Override 358 public boolean equals(Object o) { 359 if (o instanceof Conversation) { 360 Conversation conv = (Conversation) o; 361 return conv.uri.equals(uri); 362 } 363 return false; 364 } 365 366 @Override 367 public int hashCode() { 368 return uri.hashCode(); 369 } 370 371 /** 372 * Get if this conversation is marked as high priority. 373 */ 374 public boolean isImportant() { 375 return priority == UIProvider.ConversationPriority.IMPORTANT; 376 } 377 378 /** 379 * Get if this conversation is mostly dead 380 */ 381 public boolean isMostlyDead() { 382 return (convFlags & FLAG_MOSTLY_DEAD) != 0; 383 } 384 385 /** 386 * Returns true if the URI of the conversation specified as the needle was 387 * found in the collection of conversations specified as the haystack. False 388 * otherwise. This method is safe to call with null arguments. 389 * 390 * @param haystack 391 * @param needle 392 * @return true if the needle was found in the haystack, false otherwise. 393 */ 394 public final static boolean contains(Collection<Conversation> haystack, Conversation needle) { 395 // If the haystack is empty, it cannot contain anything. 396 if (haystack == null || haystack.size() <= 0) { 397 return false; 398 } 399 // The null folder exists everywhere. 400 if (needle == null) { 401 return true; 402 } 403 final long toFind = needle.id; 404 for (final Conversation c : haystack) { 405 if (toFind == c.id) { 406 return true; 407 } 408 } 409 return false; 410 } 411 412 /** 413 * Returns a collection of a single conversation. This method always returns 414 * a valid collection even if the input conversation is null. 415 * 416 * @param in a conversation, possibly null. 417 * @return a collection of the conversation. 418 */ 419 public static Collection<Conversation> listOf(Conversation in) { 420 final Collection<Conversation> target = (in == null) ? EMPTY : ImmutableList.of(in); 421 return target; 422 } 423 424 /** 425 * Get the snippet for this conversation. Masks that it may come from 426 * conversation info or the original deprecated snippet string. 427 */ 428 public String getSnippet() { 429 return conversationInfo != null && !TextUtils.isEmpty(conversationInfo.firstSnippet) ? 430 conversationInfo.firstSnippet : snippet; 431 } 432 433 /** 434 * Get the number of messages for this conversation. 435 */ 436 public int getNumMessages() { 437 return conversationInfo != null ? conversationInfo.messageCount : numMessages; 438 } 439 440 /** 441 * Get the number of drafts for this conversation. 442 */ 443 public int numDrafts() { 444 return conversationInfo != null ? conversationInfo.draftCount : numDrafts; 445 } 446 447 /** 448 * Create a human-readable string of all the conversations 449 * @param collection Any collection of conversations 450 * @return string with a human readable representation of the conversations. 451 */ 452 public static String toString(Collection<Conversation> collection) { 453 final StringBuilder out = new StringBuilder(collection.size() + " conversations:"); 454 int count = 0; 455 for (final Conversation c : collection) { 456 count++; 457 // Indent the conversations to make them easy to read in debug 458 // output. 459 out.append(" " + count + ": " + c.toString() + "\n"); 460 } 461 return out.toString(); 462 } 463 464 /** 465 * Returns an empty string if the specified string is null 466 */ 467 private static String emptyIfNull(String in) { 468 return in != null ? in : EMPTY_STRING; 469 } 470} 471