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