Conversation.java revision 7269c6fe45420cebf90841497e74d55783ea3a46
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 public 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 145 // Constituents of convFlags below 146 // Flag indicating that the item has been deleted, but will continue being 147 // shown in the list Delete/Archive of a mostly-dead item will NOT propagate 148 // the delete/archive, but WILL remove the item from the cursor 149 public static final int FLAG_MOSTLY_DEAD = 1 << 0; 150 151 /** An immutable, empty conversation list */ 152 public static final Collection<Conversation> EMPTY = Collections.emptyList(); 153 154 @Override 155 public int describeContents() { 156 return 0; 157 } 158 159 @Override 160 public void writeToParcel(Parcel dest, int flags) { 161 dest.writeLong(id); 162 dest.writeParcelable(uri, flags); 163 dest.writeString(subject); 164 dest.writeLong(dateMs); 165 dest.writeString(snippet); 166 dest.writeByte(hasAttachments ? (byte) 1 : 0); 167 dest.writeParcelable(messageListUri, 0); 168 dest.writeString(senders); 169 dest.writeInt(numMessages); 170 dest.writeInt(numDrafts); 171 dest.writeInt(sendingState); 172 dest.writeInt(priority); 173 dest.writeByte(read ? (byte) 1 : 0); 174 dest.writeByte(starred ? (byte) 1 : 0); 175 dest.writeString(rawFolders); 176 dest.writeInt(convFlags); 177 dest.writeInt(personalLevel); 178 dest.writeInt(spam ? 1 : 0); 179 dest.writeInt(phishing ? 1 : 0); 180 dest.writeInt(muted ? 1 : 0); 181 dest.writeInt(color); 182 dest.writeParcelable(accountUri, 0); 183 try { 184 dest.writeString(ConversationInfo.toString(conversationInfo)); 185 } catch (JSONException e) { 186 LogUtils.d(LOG_TAG, e, "Error adding conversationinfo to parcel"); 187 } 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 = 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 try { 216 conversationInfo = ConversationInfo.fromString(in.readString()); 217 } catch (JSONException e) { 218 LogUtils.d(LOG_TAG, e, "Error retrieving conversation info from parcel"); 219 } 220 } 221 222 @Override 223 public String toString() { 224 return "[conversation id=" + id + ", subject =" + subject + "]"; 225 } 226 227 public static final Creator<Conversation> CREATOR = new Creator<Conversation>() { 228 229 @Override 230 public Conversation createFromParcel(Parcel source) { 231 return new Conversation(source); 232 } 233 234 @Override 235 public Conversation[] newArray(int size) { 236 return new Conversation[size]; 237 } 238 239 }; 240 241 public static final Uri MOVE_CONVERSATIONS_URI = Uri.parse("content://moveconversations"); 242 243 /** 244 * The column that needs to be updated to change the read state of a conversation. 245 */ 246 public static final String UPDATE_FOLDER_COLUMN = ConversationColumns.RAW_FOLDERS; 247 248 private static final String LOG_TAG = LogTag.getLogTag(); 249 250 public Conversation(Cursor cursor) { 251 if (cursor != null) { 252 id = cursor.getLong(UIProvider.CONVERSATION_ID_COLUMN); 253 uri = Uri.parse(cursor.getString(UIProvider.CONVERSATION_URI_COLUMN)); 254 dateMs = cursor.getLong(UIProvider.CONVERSATION_DATE_RECEIVED_MS_COLUMN); 255 subject = cursor.getString(UIProvider.CONVERSATION_SUBJECT_COLUMN); 256 // Don't allow null subject 257 if (subject == null) { 258 subject = ""; 259 } 260 snippet = cursor.getString(UIProvider.CONVERSATION_SNIPPET_COLUMN); 261 hasAttachments = cursor.getInt(UIProvider.CONVERSATION_HAS_ATTACHMENTS_COLUMN) != 0; 262 String messageList = cursor.getString(UIProvider.CONVERSATION_MESSAGE_LIST_URI_COLUMN); 263 messageListUri = !TextUtils.isEmpty(messageList) ? Uri.parse(messageList) : null; 264 numMessages = cursor.getInt(UIProvider.CONVERSATION_NUM_MESSAGES_COLUMN); 265 numDrafts = cursor.getInt(UIProvider.CONVERSATION_NUM_DRAFTS_COLUMN); 266 sendingState = cursor.getInt(UIProvider.CONVERSATION_SENDING_STATE_COLUMN); 267 priority = cursor.getInt(UIProvider.CONVERSATION_PRIORITY_COLUMN); 268 read = cursor.getInt(UIProvider.CONVERSATION_READ_COLUMN) != 0; 269 starred = cursor.getInt(UIProvider.CONVERSATION_STARRED_COLUMN) != 0; 270 rawFolders = cursor.getString(UIProvider.CONVERSATION_RAW_FOLDERS_COLUMN); 271 convFlags = cursor.getInt(UIProvider.CONVERSATION_FLAGS_COLUMN); 272 personalLevel = cursor.getInt(UIProvider.CONVERSATION_PERSONAL_LEVEL_COLUMN); 273 spam = cursor.getInt(UIProvider.CONVERSATION_IS_SPAM_COLUMN) != 0; 274 phishing = cursor.getInt(UIProvider.CONVERSATION_IS_PHISHING_COLUMN) != 0; 275 muted = cursor.getInt(UIProvider.CONVERSATION_MUTED_COLUMN) != 0; 276 color = cursor.getInt(UIProvider.CONVERSATION_COLOR_COLUMN); 277 String account = cursor.getString(UIProvider.CONVERSATION_ACCOUNT_URI_COLUMN); 278 accountUri = !TextUtils.isEmpty(account) ? Uri.parse(account) : null; 279 position = NO_POSITION; 280 localDeleteOnUpdate = false; 281 senders = cursor.getString(UIProvider.CONVERSATION_SENDER_INFO_COLUMN); 282 try { 283 conversationInfo = ConversationInfo.fromString(cursor 284 .getString(UIProvider.CONVERSATION_INFO_COLUMN)); 285 } catch (JSONException e) { 286 LogUtils.w(LOG_TAG, e, 287 "Unable to instantiate ConversationInfo. Try to continue anyway"); 288 } 289 } 290 } 291 292 public Conversation() { 293 } 294 295 public static Conversation create(long id, Uri uri, String subject, long dateMs, 296 String snippet, boolean hasAttachment, Uri messageListUri, String senders, 297 int numMessages, int numDrafts, int sendingState, int priority, boolean read, 298 boolean starred, String rawFolders, int convFlags, 299 int personalLevel, boolean spam, boolean phishing, boolean muted, Uri accountUri, 300 ConversationInfo conversationInfo) { 301 302 final Conversation conversation = new Conversation(); 303 304 conversation.id = id; 305 conversation.uri = uri; 306 conversation.subject = subject; 307 conversation.dateMs = dateMs; 308 conversation.snippet = snippet; 309 conversation.hasAttachments = hasAttachment; 310 conversation.messageListUri = messageListUri; 311 conversation.senders = senders; 312 conversation.numMessages = numMessages; 313 conversation.numDrafts = numDrafts; 314 conversation.sendingState = sendingState; 315 conversation.priority = priority; 316 conversation.read = read; 317 conversation.starred = starred; 318 conversation.rawFolders = rawFolders; 319 conversation.convFlags = convFlags; 320 conversation.personalLevel = personalLevel; 321 conversation.spam = spam; 322 conversation.phishing = phishing; 323 conversation.muted = muted; 324 conversation.color = 0; 325 conversation.accountUri = accountUri; 326 conversation.conversationInfo = conversationInfo; 327 return conversation; 328 } 329 330 public ArrayList<Folder> getRawFolders() { 331 if (cachedRawFolders == null) { 332 // Create cached folders. 333 if (!TextUtils.isEmpty(rawFolders)) { 334 cachedRawFolders = Folder.getFoldersArray(rawFolders); 335 } else { 336 return new ArrayList<Folder>(); 337 } 338 } 339 return cachedRawFolders; 340 } 341 342 @Override 343 public boolean equals(Object o) { 344 if (o instanceof Conversation) { 345 Conversation conv = (Conversation) o; 346 return conv.uri.equals(uri); 347 } 348 return false; 349 } 350 351 @Override 352 public int hashCode() { 353 return uri.hashCode(); 354 } 355 356 /** 357 * Get if this conversation is marked as high priority. 358 */ 359 public boolean isImportant() { 360 return priority == UIProvider.ConversationPriority.IMPORTANT; 361 } 362 363 /** 364 * Get if this conversation is mostly dead 365 */ 366 public boolean isMostlyDead() { 367 return (convFlags & FLAG_MOSTLY_DEAD) != 0; 368 } 369 370 /** 371 * Returns true if the URI of the conversation specified as the needle was 372 * found in the collection of conversations specified as the haystack. False 373 * otherwise. This method is safe to call with null arguments. 374 * 375 * @param haystack 376 * @param needle 377 * @return true if the needle was found in the haystack, false otherwise. 378 */ 379 public final static boolean contains(Collection<Conversation> haystack, Conversation needle) { 380 // If the haystack is empty, it cannot contain anything. 381 if (haystack == null || haystack.size() <= 0) { 382 return false; 383 } 384 // The null folder exists everywhere. 385 if (needle == null) { 386 return true; 387 } 388 final long toFind = needle.id; 389 for (final Conversation c : haystack) { 390 if (toFind == c.id) { 391 return true; 392 } 393 } 394 return false; 395 } 396 397 /** 398 * Returns a collection of a single conversation. This method always returns 399 * a valid collection even if the input conversation is null. 400 * 401 * @param in a conversation, possibly null. 402 * @return a collection of the conversation. 403 */ 404 public static Collection<Conversation> listOf(Conversation in) { 405 final Collection<Conversation> target = (in == null) ? EMPTY : ImmutableList.of(in); 406 return target; 407 } 408 409 /** 410 * Get the snippet for this conversation. Masks that it may come from 411 * conversation info or the original deprecated snippet string. 412 */ 413 public String getSnippet() { 414 return conversationInfo != null && !TextUtils.isEmpty(conversationInfo.firstSnippet) ? 415 conversationInfo.firstSnippet : snippet; 416 } 417 418 /** 419 * Create a human-readable string of all the conversations 420 * 421 * @param collection Any collection of conversations 422 * @return string with a human readable representation of the conversations. 423 */ 424 public static String toString(Collection<Conversation> collection) { 425 final StringBuilder out = new StringBuilder(collection.size() + " conversations:"); 426 int count = 0; 427 for (final Conversation c : collection) { 428 count++; 429 // Indent the conversations to make them easy to read in debug 430 // output. 431 out.append(" " + count + ": " + c.toString() + "\n"); 432 } 433 return out.toString(); 434 } 435} 436