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