Message.java revision 5ed6311d6beb968e83585a43d7cdf5909e0a2cf6
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.content.AsyncQueryHandler; 20import android.content.ContentValues; 21import android.database.Cursor; 22import android.net.Uri; 23import android.os.Parcel; 24import android.os.Parcelable; 25import android.provider.BaseColumns; 26import android.text.TextUtils; 27import android.text.util.Rfc822Token; 28import android.text.util.Rfc822Tokenizer; 29 30import com.android.mail.providers.UIProvider.MessageColumns; 31import com.android.mail.utils.Utils; 32import com.google.common.base.Objects; 33 34import java.util.Collections; 35import java.util.List; 36import java.util.regex.Pattern; 37 38 39public class Message implements Parcelable { 40 /** 41 * Regex pattern used to look for any inline images in message bodies, including Gmail-hosted 42 * relative-URL images, Gmail emoticons, and any external inline images (although we usually 43 * count on the server to detect external images). 44 */ 45 private static Pattern INLINE_IMAGE_PATTERN = Pattern.compile("<img\\s+[^>]*src=", 46 Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); 47 48 /** 49 * @see BaseColumns#_ID 50 */ 51 public long id; 52 /** 53 * @see UIProvider.MessageColumns#SERVER_ID 54 */ 55 public String serverId; 56 /** 57 * @see UIProvider.MessageColumns#URI 58 */ 59 public Uri uri; 60 /** 61 * @see UIProvider.MessageColumns#CONVERSATION_ID 62 */ 63 public Uri conversationUri; 64 /** 65 * @see UIProvider.MessageColumns#SUBJECT 66 */ 67 public String subject; 68 /** 69 * @see UIProvider.MessageColumns#SNIPPET 70 */ 71 public String snippet; 72 /** 73 * @see UIProvider.MessageColumns#FROM 74 */ 75 public String from; 76 /** 77 * @see UIProvider.MessageColumns#TO 78 */ 79 public String to; 80 /** 81 * @see UIProvider.MessageColumns#CC 82 */ 83 public String cc; 84 /** 85 * @see UIProvider.MessageColumns#BCC 86 */ 87 public String bcc; 88 /** 89 * @see UIProvider.MessageColumns#REPLY_TO 90 */ 91 public String replyTo; 92 /** 93 * @see UIProvider.MessageColumns#DATE_RECEIVED_MS 94 */ 95 public long dateReceivedMs; 96 /** 97 * @see UIProvider.MessageColumns#BODY_HTML 98 */ 99 public String bodyHtml; 100 /** 101 * @see UIProvider.MessageColumns#BODY_TEXT 102 */ 103 public String bodyText; 104 /** 105 * @see UIProvider.MessageColumns#EMBEDS_EXTERNAL_RESOURCES 106 */ 107 public boolean embedsExternalResources; 108 /** 109 * @see UIProvider.MessageColumns#REF_MESSAGE_ID 110 */ 111 public String refMessageId; 112 /** 113 * @see UIProvider.MessageColumns#DRAFT_TYPE 114 */ 115 public int draftType; 116 /** 117 * @see UIProvider.MessageColumns#APPEND_REF_MESSAGE_CONTENT 118 */ 119 public boolean appendRefMessageContent; 120 /** 121 * @see UIProvider.MessageColumns#HAS_ATTACHMENTS 122 */ 123 public boolean hasAttachments; 124 /** 125 * @see UIProvider.MessageColumns#ATTACHMENT_LIST_URI 126 */ 127 public Uri attachmentListUri; 128 /** 129 * @see UIProvider.MessageColumns#MESSAGE_FLAGS 130 */ 131 public long messageFlags; 132 /** 133 * @see UIProvider.MessageColumns#SAVE_MESSAGE_URI 134 */ 135 public String saveUri; 136 /** 137 * @see UIProvider.MessageColumns#SEND_MESSAGE_URI 138 */ 139 public String sendUri; 140 /** 141 * @see UIProvider.MessageColumns#ALWAYS_SHOW_IMAGES 142 */ 143 public boolean alwaysShowImages; 144 /** 145 * @see UIProvider.MessageColumns#READ 146 */ 147 public boolean read; 148 /** 149 * @see UIProvider.MessageColumns#STARRED 150 */ 151 public boolean starred; 152 /** 153 * @see UIProvider.MessageColumns#QUOTE_START_POS 154 */ 155 public int quotedTextOffset; 156 /** 157 * @see UIProvider.MessageColumns#ATTACHMENTS 158 */ 159 public String attachmentsJson; 160 /** 161 * @see UIProvider.MessageColumns#MESSAGE_ACCOUNT_URI 162 */ 163 public Uri accountUri; 164 /** 165 * @see UIProvider.MessageColumns#EVENT_INTENT_URI 166 */ 167 public Uri eventIntentUri; 168 /** 169 * @see UIProvider.MessageColumns#SPAM_WARNING_STRING 170 */ 171 public String spamWarningString; 172 /** 173 * @see UIProvider.MessageColumns#SPAM_WARNING_LEVEL 174 */ 175 public int spamWarningLevel; 176 /** 177 * @see UIProvider.MessageColumns#SPAM_WARNING_LINK_TYPE 178 */ 179 public int spamLinkType; 180 /** 181 * @see UIProvider.MessageColumns#VIA_DOMAIN 182 */ 183 public String viaDomain; 184 /** 185 * @see UIProvider.MessageColumns#IS_SENDING 186 */ 187 public boolean isSending; 188 189 private transient String[] mFromAddresses = null; 190 private transient String[] mToAddresses = null; 191 private transient String[] mCcAddresses = null; 192 private transient String[] mBccAddresses = null; 193 private transient String[] mReplyToAddresses = null; 194 195 private transient List<Attachment> mAttachments = null; 196 private Rfc822Tokenizer mTokenizer = new Rfc822Tokenizer(); 197 @Override 198 public int describeContents() { 199 return 0; 200 } 201 202 @Override 203 public boolean equals(Object o) { 204 return this == o || (o != null && o instanceof Message 205 && Objects.equal(uri, ((Message) o).uri)); 206 } 207 208 @Override 209 public int hashCode() { 210 return uri == null ? 0 : uri.hashCode(); 211 } 212 213 @Override 214 public void writeToParcel(Parcel dest, int flags) { 215 dest.writeLong(id); 216 dest.writeString(serverId); 217 dest.writeParcelable(uri, 0); 218 dest.writeParcelable(conversationUri, 0); 219 dest.writeString(subject); 220 dest.writeString(snippet); 221 dest.writeString(from); 222 dest.writeString(to); 223 dest.writeString(cc); 224 dest.writeString(bcc); 225 dest.writeString(replyTo); 226 dest.writeLong(dateReceivedMs); 227 dest.writeString(bodyHtml); 228 dest.writeString(bodyText); 229 dest.writeInt(embedsExternalResources ? 1 : 0); 230 dest.writeString(refMessageId); 231 dest.writeInt(draftType); 232 dest.writeInt(appendRefMessageContent ? 1 : 0); 233 dest.writeInt(hasAttachments ? 1 : 0); 234 dest.writeParcelable(attachmentListUri, 0); 235 dest.writeLong(messageFlags); 236 dest.writeString(saveUri); 237 dest.writeString(sendUri); 238 dest.writeInt(alwaysShowImages ? 1 : 0); 239 dest.writeInt(quotedTextOffset); 240 dest.writeString(attachmentsJson); 241 dest.writeParcelable(accountUri, 0); 242 dest.writeParcelable(eventIntentUri, 0); 243 dest.writeString(spamWarningString); 244 dest.writeInt(spamWarningLevel); 245 dest.writeInt(spamLinkType); 246 dest.writeString(viaDomain); 247 dest.writeInt(isSending ? 1 : 0); 248 } 249 250 private Message(Parcel in) { 251 id = in.readLong(); 252 serverId = in.readString(); 253 uri = in.readParcelable(null); 254 conversationUri = in.readParcelable(null); 255 subject = in.readString(); 256 snippet = in.readString(); 257 from = in.readString(); 258 to = in.readString(); 259 cc = in.readString(); 260 bcc = in.readString(); 261 replyTo = in.readString(); 262 dateReceivedMs = in.readLong(); 263 bodyHtml = in.readString(); 264 bodyText = in.readString(); 265 embedsExternalResources = in.readInt() != 0; 266 refMessageId = in.readString(); 267 draftType = in.readInt(); 268 appendRefMessageContent = in.readInt() != 0; 269 hasAttachments = in.readInt() != 0; 270 attachmentListUri = in.readParcelable(null); 271 messageFlags = in.readLong(); 272 saveUri = in.readString(); 273 sendUri = in.readString(); 274 alwaysShowImages = in.readInt() != 0; 275 quotedTextOffset = in.readInt(); 276 attachmentsJson = in.readString(); 277 accountUri = in.readParcelable(null); 278 eventIntentUri = in.readParcelable(null); 279 spamWarningString = in.readString(); 280 spamWarningLevel = in.readInt(); 281 spamLinkType = in.readInt(); 282 viaDomain = in.readString(); 283 isSending = in.readInt() != 0; 284 } 285 286 public Message() { 287 288 } 289 290 @Override 291 public String toString() { 292 return "[message id=" + id + "]"; 293 } 294 295 public static final Creator<Message> CREATOR = new Creator<Message>() { 296 297 @Override 298 public Message createFromParcel(Parcel source) { 299 return new Message(source); 300 } 301 302 @Override 303 public Message[] newArray(int size) { 304 return new Message[size]; 305 } 306 307 }; 308 309 public Message(Cursor cursor) { 310 if (cursor != null) { 311 id = cursor.getLong(UIProvider.MESSAGE_ID_COLUMN); 312 serverId = cursor.getString(UIProvider.MESSAGE_SERVER_ID_COLUMN); 313 final String messageUriStr = cursor.getString(UIProvider.MESSAGE_URI_COLUMN); 314 uri = !TextUtils.isEmpty(messageUriStr) ? Uri.parse(messageUriStr) : null; 315 final String convUriStr = cursor.getString(UIProvider.MESSAGE_CONVERSATION_URI_COLUMN); 316 conversationUri = !TextUtils.isEmpty(convUriStr) ? Uri.parse(convUriStr) : null; 317 subject = cursor.getString(UIProvider.MESSAGE_SUBJECT_COLUMN); 318 snippet = cursor.getString(UIProvider.MESSAGE_SNIPPET_COLUMN); 319 from = cursor.getString(UIProvider.MESSAGE_FROM_COLUMN); 320 to = cursor.getString(UIProvider.MESSAGE_TO_COLUMN); 321 cc = cursor.getString(UIProvider.MESSAGE_CC_COLUMN); 322 bcc = cursor.getString(UIProvider.MESSAGE_BCC_COLUMN); 323 replyTo = cursor.getString(UIProvider.MESSAGE_REPLY_TO_COLUMN); 324 dateReceivedMs = cursor.getLong(UIProvider.MESSAGE_DATE_RECEIVED_MS_COLUMN); 325 bodyHtml = cursor.getString(UIProvider.MESSAGE_BODY_HTML_COLUMN); 326 bodyText = cursor.getString(UIProvider.MESSAGE_BODY_TEXT_COLUMN); 327 embedsExternalResources = cursor 328 .getInt(UIProvider.MESSAGE_EMBEDS_EXTERNAL_RESOURCES_COLUMN) != 0; 329 refMessageId = cursor.getString(UIProvider.MESSAGE_REF_MESSAGE_ID_COLUMN); 330 draftType = cursor.getInt(UIProvider.MESSAGE_DRAFT_TYPE_COLUMN); 331 appendRefMessageContent = cursor 332 .getInt(UIProvider.MESSAGE_APPEND_REF_MESSAGE_CONTENT_COLUMN) != 0; 333 hasAttachments = cursor.getInt(UIProvider.MESSAGE_HAS_ATTACHMENTS_COLUMN) != 0; 334 final String attachmentsUri = cursor 335 .getString(UIProvider.MESSAGE_ATTACHMENT_LIST_URI_COLUMN); 336 attachmentListUri = hasAttachments && !TextUtils.isEmpty(attachmentsUri) ? Uri 337 .parse(attachmentsUri) : null; 338 messageFlags = cursor.getLong(UIProvider.MESSAGE_FLAGS_COLUMN); 339 saveUri = cursor 340 .getString(UIProvider.MESSAGE_SAVE_URI_COLUMN); 341 sendUri = cursor 342 .getString(UIProvider.MESSAGE_SEND_URI_COLUMN); 343 alwaysShowImages = cursor.getInt(UIProvider.MESSAGE_ALWAYS_SHOW_IMAGES_COLUMN) != 0; 344 read = cursor.getInt(UIProvider.MESSAGE_READ_COLUMN) != 0; 345 starred = cursor.getInt(UIProvider.MESSAGE_STARRED_COLUMN) != 0; 346 quotedTextOffset = cursor.getInt(UIProvider.QUOTED_TEXT_OFFSET_COLUMN); 347 attachmentsJson = cursor.getString(UIProvider.MESSAGE_ATTACHMENTS_COLUMN); 348 String accountUriString = cursor.getString(UIProvider.MESSAGE_ACCOUNT_URI_COLUMN); 349 accountUri = !TextUtils.isEmpty(accountUriString) ? Uri.parse(accountUriString) : null; 350 eventIntentUri = 351 Utils.getValidUri(cursor.getString(UIProvider.MESSAGE_EVENT_INTENT_COLUMN)); 352 spamWarningString = 353 cursor.getString(UIProvider.MESSAGE_SPAM_WARNING_STRING_ID_COLUMN); 354 spamWarningLevel = cursor.getInt(UIProvider.MESSAGE_SPAM_WARNING_LEVEL_COLUMN); 355 spamLinkType = cursor.getInt(UIProvider.MESSAGE_SPAM_WARNING_LINK_TYPE_COLUMN); 356 viaDomain = cursor.getString(UIProvider.MESSAGE_VIA_DOMAIN_COLUMN); 357 isSending = cursor.getInt(UIProvider.MESSAGE_IS_SENDING_COLUMN) != 0; 358 } 359 } 360 361 public boolean isFlaggedReplied() { 362 return (messageFlags & UIProvider.MessageFlags.REPLIED) == 363 UIProvider.MessageFlags.REPLIED; 364 } 365 366 public boolean isFlaggedForwarded() { 367 return (messageFlags & UIProvider.MessageFlags.FORWARDED) == 368 UIProvider.MessageFlags.FORWARDED; 369 } 370 371 public boolean isFlaggedCalendarInvite() { 372 return (messageFlags & UIProvider.MessageFlags.CALENDAR_INVITE) == 373 UIProvider.MessageFlags.CALENDAR_INVITE; 374 } 375 376 public synchronized String[] getFromAddresses() { 377 if (mFromAddresses == null) { 378 mFromAddresses = tokenizeAddresses(from); 379 } 380 return mFromAddresses; 381 } 382 383 public synchronized String[] getToAddresses() { 384 if (mToAddresses == null) { 385 mToAddresses = tokenizeAddresses(to); 386 } 387 return mToAddresses; 388 } 389 390 public synchronized String[] getCcAddresses() { 391 if (mCcAddresses == null) { 392 mCcAddresses = tokenizeAddresses(cc); 393 } 394 return mCcAddresses; 395 } 396 397 public synchronized String[] getBccAddresses() { 398 if (mBccAddresses == null) { 399 mBccAddresses = tokenizeAddresses(bcc); 400 } 401 return mBccAddresses; 402 } 403 404 public synchronized String[] getReplyToAddresses() { 405 if (mReplyToAddresses == null) { 406 mReplyToAddresses = tokenizeAddresses(replyTo); 407 } 408 return mReplyToAddresses; 409 } 410 411 private String[] tokenizeAddresses(String addresses) { 412 if (TextUtils.isEmpty(addresses)) { 413 return new String[0]; 414 } 415 Rfc822Token[] tokens = mTokenizer.tokenize(addresses); 416 String[] strings = new String[tokens.length]; 417 for (int i = 0; i < tokens.length;i++) { 418 strings[i] = tokens[i].toString(); 419 } 420 return strings; 421 } 422 public synchronized List<Attachment> getAttachments() { 423 if (mAttachments == null) { 424 if (attachmentsJson != null) { 425 mAttachments = Attachment.fromJSONArray(attachmentsJson); 426 } else { 427 mAttachments = Collections.emptyList(); 428 } 429 } 430 return mAttachments; 431 } 432 433 /** 434 * Returns whether a "Show Pictures" button should initially appear for this message. If the 435 * button is shown, the message must also block all non-local images in the body. Inversely, if 436 * the button is not shown, the message must show all images within (or else the user would be 437 * stuck with no images and no way to reveal them). 438 * 439 * @return true if a "Show Pictures" button should appear. 440 */ 441 public boolean shouldShowImagePrompt() { 442 return !alwaysShowImages && embedsExternalResources(); 443 } 444 445 private boolean embedsExternalResources() { 446 return embedsExternalResources || 447 (!TextUtils.isEmpty(bodyHtml) && INLINE_IMAGE_PATTERN.matcher(bodyHtml).find()); 448 } 449 450 /** 451 * Helper method to command a provider to mark all messages from this sender with the 452 * {@link MessageColumns#ALWAYS_SHOW_IMAGES} flag set. 453 * 454 * @param handler a caller-provided handler to run the query on 455 * @param token (optional) token to identify the command to the handler 456 * @param cookie (optional) cookie to pass to the handler 457 */ 458 public void markAlwaysShowImages(AsyncQueryHandler handler, int token, Object cookie) { 459 alwaysShowImages = true; 460 461 final ContentValues values = new ContentValues(1); 462 values.put(UIProvider.MessageColumns.ALWAYS_SHOW_IMAGES, 1); 463 464 handler.startUpdate(token, cookie, uri, values, null, null); 465 } 466 467} 468