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