Conversation.java revision d97782206889a5508279b75cce19d7c0fb8a3bca
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 } 186 187 private Conversation(Parcel in) { 188 id = in.readLong(); 189 uri = in.readParcelable(null); 190 subject = in.readString(); 191 dateMs = in.readLong(); 192 snippet = in.readString(); 193 hasAttachments = (in.readByte() != 0); 194 messageListUri = in.readParcelable(null); 195 senders = in.readString(); 196 numMessages = in.readInt(); 197 numDrafts = in.readInt(); 198 sendingState = in.readInt(); 199 priority = in.readInt(); 200 read = (in.readByte() != 0); 201 starred = (in.readByte() != 0); 202 folderList = in.readString(); 203 rawFolders = in.readString(); 204 convFlags = in.readInt(); 205 personalLevel = in.readInt(); 206 spam = in.readInt() != 0; 207 phishing = in.readInt() != 0; 208 muted = in.readInt() != 0; 209 color = in.readInt(); 210 accountUri = in.readParcelable(null); 211 position = NO_POSITION; 212 localDeleteOnUpdate = false; 213 } 214 215 @Override 216 public String toString() { 217 return "[conversation id=" + id + ", subject =" + subject + "]"; 218 } 219 220 public static final Creator<Conversation> CREATOR = new Creator<Conversation>() { 221 222 @Override 223 public Conversation createFromParcel(Parcel source) { 224 return new Conversation(source); 225 } 226 227 @Override 228 public Conversation[] newArray(int size) { 229 return new Conversation[size]; 230 } 231 232 }; 233 234 public static final Uri MOVE_CONVERSATIONS_URI = Uri.parse("content://moveconversations"); 235 236 /** 237 * The columns that need to be updated to change the read state of a conversation. 238 */ 239 public static final String[] UPDATE_FOLDER_COLUMNS = new String[] { 240 ConversationColumns.FOLDER_LIST, ConversationColumns.RAW_FOLDERS 241 }; 242 243 private static final String LOG_TAG = LogTag.getLogTag(); 244 245 public Conversation(Cursor cursor) { 246 if (cursor != null) { 247 id = cursor.getLong(UIProvider.CONVERSATION_ID_COLUMN); 248 uri = Uri.parse(cursor.getString(UIProvider.CONVERSATION_URI_COLUMN)); 249 dateMs = cursor.getLong(UIProvider.CONVERSATION_DATE_RECEIVED_MS_COLUMN); 250 subject = cursor.getString(UIProvider.CONVERSATION_SUBJECT_COLUMN); 251 // Don't allow null subject 252 if (subject == null) { 253 subject = ""; 254 } 255 snippet = cursor.getString(UIProvider.CONVERSATION_SNIPPET_COLUMN); 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 numMessages = cursor.getInt(UIProvider.CONVERSATION_NUM_MESSAGES_COLUMN); 260 numDrafts = cursor.getInt(UIProvider.CONVERSATION_NUM_DRAFTS_COLUMN); 261 sendingState = cursor.getInt(UIProvider.CONVERSATION_SENDING_STATE_COLUMN); 262 priority = cursor.getInt(UIProvider.CONVERSATION_PRIORITY_COLUMN); 263 read = cursor.getInt(UIProvider.CONVERSATION_READ_COLUMN) != 0; 264 starred = cursor.getInt(UIProvider.CONVERSATION_STARRED_COLUMN) != 0; 265 folderList = cursor.getString(UIProvider.CONVERSATION_FOLDER_LIST_COLUMN); 266 rawFolders = cursor.getString(UIProvider.CONVERSATION_RAW_FOLDERS_COLUMN); 267 convFlags = cursor.getInt(UIProvider.CONVERSATION_FLAGS_COLUMN); 268 personalLevel = cursor.getInt(UIProvider.CONVERSATION_PERSONAL_LEVEL_COLUMN); 269 spam = cursor.getInt(UIProvider.CONVERSATION_IS_SPAM_COLUMN) != 0; 270 phishing = cursor.getInt(UIProvider.CONVERSATION_IS_PHISHING_COLUMN) != 0; 271 muted = cursor.getInt(UIProvider.CONVERSATION_MUTED_COLUMN) != 0; 272 color = cursor.getInt(UIProvider.CONVERSATION_COLOR_COLUMN); 273 String account = cursor.getString(UIProvider.CONVERSATION_ACCOUNT_URI_COLUMN); 274 accountUri = !TextUtils.isEmpty(account) ? Uri.parse(account) : null; 275 position = NO_POSITION; 276 localDeleteOnUpdate = false; 277 senders = cursor.getString(UIProvider.CONVERSATION_SENDER_INFO_COLUMN); 278 try { 279 conversationInfo = ConversationInfo.fromString(cursor 280 .getString(UIProvider.CONVERSATION_INFO_COLUMN)); 281 } catch (JSONException e) { 282 LogUtils.w(LOG_TAG, e, 283 "Unable to instantiate ConversationInfo. Try to continue anyway"); 284 } 285 } 286 } 287 288 public Conversation() { 289 } 290 291 public static Conversation create(long id, Uri uri, String subject, long dateMs, 292 String snippet, boolean hasAttachment, Uri messageListUri, String senders, 293 int numMessages, int numDrafts, int sendingState, int priority, boolean read, 294 boolean starred, String folderList, String rawFolders, int convFlags, 295 int personalLevel, boolean spam, boolean phishing, boolean muted, Uri accountUri) { 296 297 final Conversation conversation = new Conversation(); 298 299 conversation.id = id; 300 conversation.uri = uri; 301 conversation.subject = subject; 302 conversation.dateMs = dateMs; 303 conversation.snippet = snippet; 304 conversation.hasAttachments = hasAttachment; 305 conversation.messageListUri = messageListUri; 306 conversation.senders = senders; 307 conversation.numMessages = numMessages; 308 conversation.numDrafts = numDrafts; 309 conversation.sendingState = sendingState; 310 conversation.priority = priority; 311 conversation.read = read; 312 conversation.starred = starred; 313 conversation.folderList = folderList; 314 conversation.rawFolders = rawFolders; 315 conversation.convFlags = convFlags; 316 conversation.personalLevel = personalLevel; 317 conversation.spam = spam; 318 conversation.phishing = phishing; 319 conversation.muted = muted; 320 conversation.color = 0; 321 conversation.accountUri = accountUri; 322 return conversation; 323 } 324 325 @Override 326 public boolean equals(Object o) { 327 if (o instanceof Conversation) { 328 Conversation conv = (Conversation) o; 329 return conv.uri.equals(uri); 330 } 331 return false; 332 } 333 334 @Override 335 public int hashCode() { 336 return uri.hashCode(); 337 } 338 339 /** 340 * Get if this conversation is marked as high priority. 341 */ 342 public boolean isImportant() { 343 return priority == UIProvider.ConversationPriority.IMPORTANT; 344 } 345 346 /** 347 * Get if this conversation is mostly dead 348 */ 349 public boolean isMostlyDead() { 350 return (convFlags & FLAG_MOSTLY_DEAD) != 0; 351 } 352 353 /** 354 * Returns true if the URI of the conversation specified as the needle was 355 * found in the collection of conversations specified as the haystack. False 356 * otherwise. This method is safe to call with nullarguments. 357 * 358 * @param haystack 359 * @param needle 360 * @return true if the needle was found in the haystack, false otherwise. 361 */ 362 public final static boolean contains(Collection<Conversation> haystack, Conversation needle) { 363 // If the haystack is empty, it cannot contain anything. 364 if (haystack == null || haystack.size() <= 0) { 365 return false; 366 } 367 // The null folder exists everywhere. 368 if (needle == null) { 369 return true; 370 } 371 final long toFind = needle.id; 372 for (final Conversation c : haystack) { 373 if (toFind == c.id) { 374 return true; 375 } 376 } 377 return false; 378 } 379 380 /** 381 * Returns a collection of a single conversation. This method always returns 382 * a valid collection even if the input conversation is null. 383 * 384 * @param in a conversation, possibly null. 385 * @return a collection of the conversation. 386 */ 387 public static Collection<Conversation> listOf(Conversation in) { 388 final Collection<Conversation> target = (in == null) ? EMPTY : ImmutableList.of(in); 389 return target; 390 } 391 392 /** 393 * Get the snippet for this conversation. Masks that it may come from 394 * conversation info or the original deprecated snippet string. 395 */ 396 public String getSnippet() { 397 return conversationInfo != null && !TextUtils.isEmpty(conversationInfo.firstSnippet) ? 398 conversationInfo.firstSnippet : snippet; 399 } 400 401 /** 402 * Create a human-readable string of all the conversations 403 * 404 * @param collection Any collection of conversations 405 * @return string with a human readable representation of the conversations. 406 */ 407 public static String toString(Collection<Conversation> collection) { 408 final StringBuilder out = new StringBuilder(collection.size() + " conversations:"); 409 int count = 0; 410 for (final Conversation c : collection) { 411 count++; 412 // Indent the conversations to make them easy to read in debug 413 // output. 414 out.append(" " + count + ": " + c.toString() + "\n"); 415 } 416 return out.toString(); 417 } 418} 419