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