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