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