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