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