1 /* 2 * Copyright (C) 2009 The Android Open Source Project 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 17 package com.android.emailcommon.provider; 18 19 import android.content.ContentProviderOperation; 20 import android.content.ContentProviderResult; 21 import android.content.ContentResolver; 22 import android.content.ContentUris; 23 import android.content.ContentValues; 24 import android.content.Context; 25 import android.content.OperationApplicationException; 26 import android.content.res.Resources; 27 import android.database.ContentObservable; 28 import android.database.ContentObserver; 29 import android.database.Cursor; 30 import android.net.Uri; 31 import android.os.Environment; 32 import android.os.Looper; 33 import android.os.Parcel; 34 import android.os.Parcelable; 35 import android.os.RemoteException; 36 import android.provider.BaseColumns; 37 38 import com.android.emailcommon.Logging; 39 import com.android.emailcommon.R; 40 import com.android.emailcommon.utility.TextUtilities; 41 import com.android.emailcommon.utility.Utility; 42 import com.android.mail.providers.UIProvider; 43 import com.android.mail.utils.LogUtils; 44 import com.google.common.annotations.VisibleForTesting; 45 46 import org.apache.commons.io.IOUtils; 47 48 import java.io.File; 49 import java.io.IOException; 50 import java.io.InputStream; 51 import java.lang.ref.WeakReference; 52 import java.util.ArrayList; 53 54 55 /** 56 * EmailContent is the superclass of the various classes of content stored by EmailProvider. 57 * 58 * It is intended to include 1) column definitions for use with the Provider, and 2) convenience 59 * methods for saving and retrieving content from the Provider. 60 * 61 * This class will be used by 1) the Email process (which includes the application and 62 * EmaiLProvider) as well as 2) the Exchange process (which runs independently). It will 63 * necessarily be cloned for use in these two cases. 64 * 65 * Conventions used in naming columns: 66 * BaseColumns._ID is the primary key for all Email records 67 * The SyncColumns interface is used by all classes that are synced to the server directly 68 * (Mailbox and Email) 69 * 70 * <name>_KEY always refers to a foreign key 71 * <name>_ID always refers to a unique identifier (whether on client, server, etc.) 72 * 73 */ 74 public abstract class EmailContent { 75 public static final int NOTIFICATION_MAILBOX_ID_COLUMN = 0; 76 public static final int NOTIFICATION_MAILBOX_UNREAD_COUNT_COLUMN = 1; 77 public static final int NOTIFICATION_MAILBOX_UNSEEN_COUNT_COLUMN = 2; 78 79 // All classes share this 80 // Use BaseColumns._ID instead 81 @Deprecated 82 public static final String RECORD_ID = "_id"; 83 84 public static final String[] COUNT_COLUMNS = {"count(*)"}; 85 86 /** 87 * This projection can be used with any of the EmailContent classes, when all you need 88 * is a list of id's. Use ID_PROJECTION_COLUMN to access the row data. 89 */ 90 public static final String[] ID_PROJECTION = { BaseColumns._ID }; 91 public static final int ID_PROJECTION_COLUMN = 0; 92 93 public static final String ID_SELECTION = BaseColumns._ID + " =?"; 94 95 public static final int SYNC_STATUS_NONE = UIProvider.SyncStatus.NO_SYNC; 96 public static final int SYNC_STATUS_USER = UIProvider.SyncStatus.USER_REFRESH; 97 public static final int SYNC_STATUS_BACKGROUND = UIProvider.SyncStatus.BACKGROUND_SYNC; 98 public static final int SYNC_STATUS_LIVE = UIProvider.SyncStatus.LIVE_QUERY; 99 public static final int SYNC_STATUS_INITIAL_SYNC_NEEDED = 100 UIProvider.SyncStatus.INITIAL_SYNC_NEEDED; 101 102 public static final int LAST_SYNC_RESULT_SUCCESS = UIProvider.LastSyncResult.SUCCESS; 103 public static final int LAST_SYNC_RESULT_AUTH_ERROR = UIProvider.LastSyncResult.AUTH_ERROR; 104 public static final int LAST_SYNC_RESULT_SERVER_ERROR = UIProvider.LastSyncResult.SERVER_ERROR; 105 public static final int LAST_SYNC_RESULT_SECURITY_ERROR = 106 UIProvider.LastSyncResult.SECURITY_ERROR; 107 public static final int LAST_SYNC_RESULT_CONNECTION_ERROR = 108 UIProvider.LastSyncResult.CONNECTION_ERROR; 109 public static final int LAST_SYNC_RESULT_INTERNAL_ERROR = 110 UIProvider.LastSyncResult.INTERNAL_ERROR; 111 112 // Newly created objects get this id 113 public static final int NOT_SAVED = -1; 114 // The base Uri that this piece of content came from 115 public Uri mBaseUri; 116 // Lazily initialized uri for this Content 117 private Uri mUri = null; 118 // The id of the Content 119 public long mId = NOT_SAVED; 120 121 /** 122 * Since we close the cursor we use to generate this object, and possibly create the object 123 * without using any cursor at all (eg: parcel), we need to handle observing provider changes 124 * ourselves. This content observer uses a weak reference to keep from rooting this object 125 * in the ContentResolver in case it is not properly disposed of using {@link #close(Context)} 126 */ 127 private SelfContentObserver mSelfObserver; 128 private ContentObservable mObservable; 129 130 // Write the Content into a ContentValues container toContentValues()131 public abstract ContentValues toContentValues(); 132 // Read the Content from a ContentCursor restore(Cursor cursor)133 public abstract void restore(Cursor cursor); 134 // Same as above, with the addition of a context to retrieve extra content. 135 // Body uses this to fetch the email body html/text from the provider bypassing the cursor 136 // Not always safe to call on the UI thread. restore(Context context, Cursor cursor)137 public void restore(Context context, Cursor cursor) { 138 restore(cursor); 139 } 140 141 142 public static String EMAIL_PACKAGE_NAME; 143 public static String AUTHORITY; 144 // The notifier authority is used to send notifications regarding changes to messages (insert, 145 // delete, or update) and is intended as an optimization for use by clients of message list 146 // cursors (initially, the email AppWidget). 147 public static String NOTIFIER_AUTHORITY; 148 public static Uri CONTENT_URI; 149 public static final String PARAMETER_LIMIT = "limit"; 150 151 /** 152 * Query parameter for the UI accounts query to enable suppression of the combined account. 153 */ 154 public static final String SUPPRESS_COMBINED_ACCOUNT_PARAM = "suppress_combined"; 155 public static Uri CONTENT_NOTIFIER_URI; 156 public static Uri PICK_TRASH_FOLDER_URI; 157 public static Uri PICK_SENT_FOLDER_URI; 158 public static Uri MAILBOX_NOTIFICATION_URI; 159 public static Uri MAILBOX_MOST_RECENT_MESSAGE_URI; 160 public static Uri ACCOUNT_CHECK_URI; 161 162 /** 163 * String for both the EmailProvider call, and the key for the value in the response. 164 * TODO: Eventually this ought to be a device property, not defined by the app. 165 */ 166 public static String DEVICE_FRIENDLY_NAME = "deviceFriendlyName"; 167 168 169 public static String PROVIDER_PERMISSION; 170 init(Context context)171 public static synchronized void init(Context context) { 172 if (AUTHORITY == null) { 173 final Resources res = context.getResources(); 174 EMAIL_PACKAGE_NAME = res.getString(R.string.email_package_name); 175 AUTHORITY = EMAIL_PACKAGE_NAME + ".provider"; 176 LogUtils.d("EmailContent", "init for " + AUTHORITY); 177 NOTIFIER_AUTHORITY = EMAIL_PACKAGE_NAME + ".notifier"; 178 CONTENT_URI = Uri.parse("content://" + AUTHORITY); 179 CONTENT_NOTIFIER_URI = Uri.parse("content://" + NOTIFIER_AUTHORITY); 180 PICK_TRASH_FOLDER_URI = Uri.parse("content://" + AUTHORITY + "/pickTrashFolder"); 181 PICK_SENT_FOLDER_URI = Uri.parse("content://" + AUTHORITY + "/pickSentFolder"); 182 MAILBOX_NOTIFICATION_URI = Uri.parse("content://" + AUTHORITY + "/mailboxNotification"); 183 MAILBOX_MOST_RECENT_MESSAGE_URI = Uri.parse("content://" + AUTHORITY + 184 "/mailboxMostRecentMessage"); 185 ACCOUNT_CHECK_URI = Uri.parse("content://" + AUTHORITY + "/accountCheck"); 186 PROVIDER_PERMISSION = EMAIL_PACKAGE_NAME + ".permission.ACCESS_PROVIDER"; 187 // Initialize subclasses 188 Account.initAccount(); 189 Mailbox.initMailbox(); 190 QuickResponse.initQuickResponse(); 191 HostAuth.initHostAuth(); 192 Credential.initCredential(); 193 Policy.initPolicy(); 194 Message.initMessage(); 195 MessageMove.init(); 196 MessageStateChange.init(); 197 Body.initBody(); 198 Attachment.initAttachment(); 199 } 200 } 201 202 warnIfUiThread()203 private static void warnIfUiThread() { 204 if (Looper.getMainLooper().getThread() == Thread.currentThread()) { 205 LogUtils.w(Logging.LOG_TAG, new Throwable(), "Method called on the UI thread"); 206 } 207 } 208 isInitialSyncKey(final String syncKey)209 public static boolean isInitialSyncKey(final String syncKey) { 210 return syncKey == null || syncKey.isEmpty() || syncKey.equals("0"); 211 } 212 213 // The Uri is lazily initialized getUri()214 public Uri getUri() { 215 if (mUri == null) { 216 mUri = ContentUris.withAppendedId(mBaseUri, mId); 217 } 218 return mUri; 219 } 220 isSaved()221 public boolean isSaved() { 222 return mId != NOT_SAVED; 223 } 224 225 226 /** 227 * Restore a subclass of EmailContent from the database 228 * @param context the caller's context 229 * @param klass the class to restore 230 * @param contentUri the content uri of the EmailContent subclass 231 * @param contentProjection the content projection for the EmailContent subclass 232 * @param id the unique id of the object 233 * @return the instantiated object 234 */ restoreContentWithId(Context context, Class<T> klass, Uri contentUri, String[] contentProjection, long id)235 public static <T extends EmailContent> T restoreContentWithId(Context context, 236 Class<T> klass, Uri contentUri, String[] contentProjection, long id) { 237 return restoreContentWithId(context, klass, contentUri, contentProjection, id, null); 238 } 239 restoreContentWithId(final Context context, final Class<T> klass, final Uri contentUri, final String[] contentProjection, final long id, final ContentObserver observer)240 public static <T extends EmailContent> T restoreContentWithId(final Context context, 241 final Class<T> klass, final Uri contentUri, final String[] contentProjection, 242 final long id, final ContentObserver observer) { 243 warnIfUiThread(); 244 final Uri u = ContentUris.withAppendedId(contentUri, id); 245 final Cursor c = context.getContentResolver().query(u, contentProjection, null, null, null); 246 if (c == null) throw new ProviderUnavailableException(); 247 try { 248 if (c.moveToFirst()) { 249 final T content = getContent(context, c, klass); 250 if (observer != null) { 251 content.registerObserver(context, observer); 252 } 253 return content; 254 } else { 255 return null; 256 } 257 } finally { 258 c.close(); 259 } 260 } 261 262 /** 263 * Register a content observer to be notified when the data underlying this object changes 264 * @param observer ContentObserver to register 265 */ registerObserver(final Context context, final ContentObserver observer)266 public synchronized void registerObserver(final Context context, final ContentObserver observer) { 267 if (mSelfObserver == null) { 268 mSelfObserver = new SelfContentObserver(this); 269 context.getContentResolver().registerContentObserver(getContentNotificationUri(), 270 true, mSelfObserver); 271 mObservable = new ContentObservable(); 272 } 273 mObservable.registerObserver(observer); 274 } 275 276 /** 277 * Unregister a content observer previously registered with 278 * {@link #registerObserver(Context, ContentObserver)} 279 * @param observer ContentObserver to unregister 280 */ unregisterObserver(final ContentObserver observer)281 public synchronized void unregisterObserver(final ContentObserver observer) { 282 if (mObservable == null) { 283 throw new IllegalStateException("Unregistering with null observable"); 284 } 285 mObservable.unregisterObserver(observer); 286 } 287 288 /** 289 * Unregister all content observers previously registered with 290 * {@link #registerObserver(Context, ContentObserver)} 291 */ unregisterAllObservers()292 public synchronized void unregisterAllObservers() { 293 if (mObservable == null) { 294 throw new IllegalStateException("Unregistering with null observable"); 295 } 296 mObservable.unregisterAll(); 297 } 298 299 /** 300 * Unregister all content observers previously registered with 301 * {@link #registerObserver(Context, ContentObserver)} and release internal resources associated 302 * with content observing 303 */ close(final Context context)304 public synchronized void close(final Context context) { 305 if (mSelfObserver == null) { 306 return; 307 } 308 unregisterAllObservers(); 309 context.getContentResolver().unregisterContentObserver(mSelfObserver); 310 mSelfObserver = null; 311 } 312 313 /** 314 * Returns a Uri for observing the underlying content. Subclasses that wish to implement content 315 * observing must override this method. 316 * @return Uri for registering content notifications 317 */ getContentNotificationUri()318 protected Uri getContentNotificationUri() { 319 throw new UnsupportedOperationException( 320 "Subclasses must override this method for content observation to work"); 321 } 322 323 /** 324 * This method is called when the underlying data has changed, and notifies registered observers 325 * @param selfChange true if this is a self-change notification 326 */ 327 @SuppressWarnings("deprecation") onChange(final boolean selfChange)328 public synchronized void onChange(final boolean selfChange) { 329 if (mObservable != null) { 330 mObservable.dispatchChange(selfChange); 331 } 332 } 333 334 /** 335 * A content observer that calls {@link #onChange(boolean)} when triggered 336 */ 337 private static class SelfContentObserver extends ContentObserver { 338 WeakReference<EmailContent> mContent; 339 SelfContentObserver(final EmailContent content)340 public SelfContentObserver(final EmailContent content) { 341 super(null); 342 mContent = new WeakReference<EmailContent>(content); 343 } 344 345 @Override deliverSelfNotifications()346 public boolean deliverSelfNotifications() { 347 return false; 348 } 349 350 @Override onChange(final boolean selfChange)351 public void onChange(final boolean selfChange) { 352 EmailContent content = mContent.get(); 353 if (content != null) { 354 content.onChange(false); 355 } 356 } 357 } 358 359 360 // The Content sub class must have a no-arg constructor getContent(final Context context, final Cursor cursor, final Class<T> klass)361 static public <T extends EmailContent> T getContent(final Context context, final Cursor cursor, 362 final Class<T> klass) { 363 try { 364 T content = klass.newInstance(); 365 content.mId = cursor.getLong(0); 366 content.restore(context, cursor); 367 return content; 368 } catch (IllegalAccessException e) { 369 e.printStackTrace(); 370 } catch (InstantiationException e) { 371 e.printStackTrace(); 372 } 373 return null; 374 } 375 save(Context context)376 public Uri save(Context context) { 377 if (isSaved()) { 378 throw new UnsupportedOperationException(); 379 } 380 Uri res = context.getContentResolver().insert(mBaseUri, toContentValues()); 381 mId = Long.parseLong(res.getPathSegments().get(1)); 382 return res; 383 } 384 update(Context context, ContentValues contentValues)385 public int update(Context context, ContentValues contentValues) { 386 if (!isSaved()) { 387 throw new UnsupportedOperationException(); 388 } 389 return context.getContentResolver().update(getUri(), contentValues, null, null); 390 } 391 update(Context context, Uri baseUri, long id, ContentValues contentValues)392 static public int update(Context context, Uri baseUri, long id, ContentValues contentValues) { 393 return context.getContentResolver() 394 .update(ContentUris.withAppendedId(baseUri, id), contentValues, null, null); 395 } 396 delete(Context context, Uri baseUri, long id)397 static public int delete(Context context, Uri baseUri, long id) { 398 return context.getContentResolver() 399 .delete(ContentUris.withAppendedId(baseUri, id), null, null); 400 } 401 402 /** 403 * Generic count method that can be used for any ContentProvider 404 * 405 * @param context the calling Context 406 * @param uri the Uri for the provider query 407 * @param selection as with a query call 408 * @param selectionArgs as with a query call 409 * @return the number of items matching the query (or zero) 410 */ count(Context context, Uri uri, String selection, String[] selectionArgs)411 static public int count(Context context, Uri uri, String selection, String[] selectionArgs) { 412 return Utility.getFirstRowLong(context, 413 uri, COUNT_COLUMNS, selection, selectionArgs, null, 0, 0L).intValue(); 414 } 415 416 /** 417 * Same as {@link #count(Context, Uri, String, String[])} without selection. 418 */ count(Context context, Uri uri)419 static public int count(Context context, Uri uri) { 420 return count(context, uri, null, null); 421 } 422 uriWithLimit(Uri uri, int limit)423 static public Uri uriWithLimit(Uri uri, int limit) { 424 return uri.buildUpon().appendQueryParameter(EmailContent.PARAMETER_LIMIT, 425 Integer.toString(limit)).build(); 426 } 427 428 /** 429 * no public constructor since this is a utility class 430 */ EmailContent()431 protected EmailContent() { 432 } 433 434 public interface SyncColumns { 435 // source id (string) : the source's name of this item 436 public static final String SERVER_ID = "syncServerId"; 437 // source's timestamp (long) for this item 438 public static final String SERVER_TIMESTAMP = "syncServerTimeStamp"; 439 } 440 441 public interface BodyColumns extends BaseColumns { 442 // Foreign key to the message corresponding to this body 443 public static final String MESSAGE_KEY = "messageKey"; 444 // The html content itself, not returned on query 445 public static final String HTML_CONTENT = "htmlContent"; 446 // The html content URI, for ContentResolver#openFileDescriptor() 447 public static final String HTML_CONTENT_URI = "htmlContentUri"; 448 // The plain text content itself, not returned on query 449 public static final String TEXT_CONTENT = "textContent"; 450 // The text content URI, for ContentResolver#openFileDescriptor() 451 public static final String TEXT_CONTENT_URI = "textContentUri"; 452 // Replied-to or forwarded body (in html form) 453 @Deprecated 454 public static final String HTML_REPLY = "htmlReply"; 455 // Replied-to or forwarded body (in text form) 456 @Deprecated 457 public static final String TEXT_REPLY = "textReply"; 458 // A reference to a message's unique id used in reply/forward. 459 // Protocol code can be expected to use this column in determining whether a message can be 460 // deleted safely (i.e. isn't referenced by other messages) 461 public static final String SOURCE_MESSAGE_KEY = "sourceMessageKey"; 462 // The text to be placed between a reply/forward response and the original message 463 @Deprecated 464 public static final String INTRO_TEXT = "introText"; 465 // The start of quoted text within our text content 466 public static final String QUOTED_TEXT_START_POS = "quotedTextStartPos"; 467 } 468 469 public static final class Body extends EmailContent { 470 public static final String TABLE_NAME = "Body"; 471 472 public static final String SELECTION_BY_MESSAGE_KEY = BodyColumns.MESSAGE_KEY + "=?"; 473 474 public static Uri CONTENT_URI; 475 initBody()476 public static void initBody() { 477 CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/body"); 478 } 479 480 public static final String[] CONTENT_PROJECTION = new String[] { 481 BaseColumns._ID, 482 BodyColumns.MESSAGE_KEY, 483 BodyColumns.HTML_CONTENT_URI, 484 BodyColumns.TEXT_CONTENT_URI, 485 BodyColumns.SOURCE_MESSAGE_KEY, 486 BodyColumns.QUOTED_TEXT_START_POS 487 }; 488 489 public static final int CONTENT_ID_COLUMN = 0; 490 public static final int CONTENT_MESSAGE_KEY_COLUMN = 1; 491 public static final int CONTENT_HTML_URI_COLUMN = 2; 492 public static final int CONTENT_TEXT_URI_COLUMN = 3; 493 public static final int CONTENT_SOURCE_KEY_COLUMN = 4; 494 public static final int CONTENT_QUOTED_TEXT_START_POS_COLUMN = 5; 495 496 public long mMessageKey; 497 public String mHtmlContent; 498 public String mTextContent; 499 public int mQuotedTextStartPos; 500 501 /** 502 * Points to the ID of the message being replied to or forwarded. Will always be set. 503 */ 504 public long mSourceKey; 505 Body()506 public Body() { 507 mBaseUri = CONTENT_URI; 508 } 509 510 @Override toContentValues()511 public ContentValues toContentValues() { 512 ContentValues values = new ContentValues(); 513 514 // Assign values for each row. 515 values.put(BodyColumns.MESSAGE_KEY, mMessageKey); 516 values.put(BodyColumns.HTML_CONTENT, mHtmlContent); 517 values.put(BodyColumns.TEXT_CONTENT, mTextContent); 518 values.put(BodyColumns.SOURCE_MESSAGE_KEY, mSourceKey); 519 return values; 520 } 521 522 /** 523 * Given a cursor, restore a Body from it 524 * @param cursor a cursor which must NOT be null 525 * @return the Body as restored from the cursor 526 */ restoreBodyWithCursor(final Context context, final Cursor cursor)527 private static Body restoreBodyWithCursor(final Context context, final Cursor cursor) { 528 try { 529 if (cursor.moveToFirst()) { 530 return getContent(context, cursor, Body.class); 531 } else { 532 return null; 533 } 534 } finally { 535 cursor.close(); 536 } 537 } 538 restoreBodyWithMessageId(Context context, long messageId)539 public static Body restoreBodyWithMessageId(Context context, long messageId) { 540 Cursor c = context.getContentResolver().query(Body.CONTENT_URI, 541 Body.CONTENT_PROJECTION, BodyColumns.MESSAGE_KEY + "=?", 542 new String[] {Long.toString(messageId)}, null); 543 if (c == null) throw new ProviderUnavailableException(); 544 return restoreBodyWithCursor(context, c); 545 } 546 547 /** 548 * Returns the bodyId for the given messageId, or -1 if no body is found. 549 */ lookupBodyIdWithMessageId(Context context, long messageId)550 public static long lookupBodyIdWithMessageId(Context context, long messageId) { 551 return Utility.getFirstRowLong(context, Body.CONTENT_URI, 552 ID_PROJECTION, BodyColumns.MESSAGE_KEY + "=?", 553 new String[] {Long.toString(messageId)}, null, ID_PROJECTION_COLUMN, -1L); 554 } 555 556 /** 557 * Updates the Body for a messageId with the given ContentValues. 558 * If the message has no body, a new body is inserted for the message. 559 * Warning: the argument "values" is modified by this method, setting MESSAGE_KEY. 560 */ updateBodyWithMessageId(Context context, long messageId, ContentValues values)561 public static void updateBodyWithMessageId(Context context, long messageId, 562 ContentValues values) { 563 ContentResolver resolver = context.getContentResolver(); 564 long bodyId = lookupBodyIdWithMessageId(context, messageId); 565 values.put(BodyColumns.MESSAGE_KEY, messageId); 566 if (bodyId == -1) { 567 resolver.insert(CONTENT_URI, values); 568 } else { 569 final Uri uri = ContentUris.withAppendedId(CONTENT_URI, bodyId); 570 resolver.update(uri, values, null, null); 571 } 572 } 573 574 @VisibleForTesting restoreBodySourceKey(Context context, long messageId)575 public static long restoreBodySourceKey(Context context, long messageId) { 576 return Utility.getFirstRowLong(context, Body.CONTENT_URI, 577 new String[] {BodyColumns.SOURCE_MESSAGE_KEY}, 578 BodyColumns.MESSAGE_KEY + "=?", new String[] {Long.toString(messageId)}, null, 579 0, 0L); 580 } 581 getBodyTextUriForMessageWithId(long messageId)582 public static Uri getBodyTextUriForMessageWithId(long messageId) { 583 return EmailContent.CONTENT_URI.buildUpon() 584 .appendPath("bodyText").appendPath(Long.toString(messageId)).build(); 585 } 586 getBodyHtmlUriForMessageWithId(long messageId)587 public static Uri getBodyHtmlUriForMessageWithId(long messageId) { 588 return EmailContent.CONTENT_URI.buildUpon() 589 .appendPath("bodyHtml").appendPath(Long.toString(messageId)).build(); 590 } 591 restoreBodyTextWithMessageId(Context context, long messageId)592 public static String restoreBodyTextWithMessageId(Context context, long messageId) { 593 return readBodyFromProvider(context, 594 getBodyTextUriForMessageWithId(messageId).toString()); 595 } 596 restoreBodyHtmlWithMessageId(Context context, long messageId)597 public static String restoreBodyHtmlWithMessageId(Context context, long messageId) { 598 return readBodyFromProvider(context, 599 getBodyHtmlUriForMessageWithId(messageId).toString()); 600 } 601 readBodyFromProvider(final Context context, final String uri)602 private static String readBodyFromProvider(final Context context, final String uri) { 603 String content = null; 604 try { 605 final InputStream bodyInput = 606 context.getContentResolver().openInputStream(Uri.parse(uri)); 607 try { 608 content = IOUtils.toString(bodyInput); 609 } finally { 610 bodyInput.close(); 611 } 612 } catch (final IOException e) { 613 LogUtils.v(LogUtils.TAG, e, "Exception while reading body content"); 614 } 615 return content; 616 } 617 618 @Override restore(final Cursor cursor)619 public void restore(final Cursor cursor) { 620 throw new UnsupportedOperationException("Must have context to restore Body object"); 621 } 622 623 @Override restore(final Context context, final Cursor cursor)624 public void restore(final Context context, final Cursor cursor) { 625 warnIfUiThread(); 626 mBaseUri = EmailContent.Body.CONTENT_URI; 627 mMessageKey = cursor.getLong(CONTENT_MESSAGE_KEY_COLUMN); 628 // These get overwritten below if we find a file descriptor in the respond() call, 629 // but we'll keep this here in case we want to construct a matrix cursor or something 630 // to build a Body object from. 631 mHtmlContent = readBodyFromProvider(context, cursor.getString(CONTENT_HTML_URI_COLUMN)); 632 mTextContent = readBodyFromProvider(context, cursor.getString(CONTENT_TEXT_URI_COLUMN)); 633 mSourceKey = cursor.getLong(CONTENT_SOURCE_KEY_COLUMN); 634 mQuotedTextStartPos = cursor.getInt(CONTENT_QUOTED_TEXT_START_POS_COLUMN); 635 } 636 } 637 638 public interface MessageColumns extends BaseColumns, SyncColumns { 639 // Basic columns used in message list presentation 640 // The name as shown to the user in a message list 641 public static final String DISPLAY_NAME = "displayName"; 642 // The time (millis) as shown to the user in a message list [INDEX] 643 public static final String TIMESTAMP = "timeStamp"; 644 // Message subject 645 public static final String SUBJECT = "subject"; 646 // Boolean, unread = 0, read = 1 [INDEX] 647 public static final String FLAG_READ = "flagRead"; 648 // Load state, see constants below (unloaded, partial, complete, deleted) 649 public static final String FLAG_LOADED = "flagLoaded"; 650 // Boolean, unflagged = 0, flagged (favorite) = 1 651 public static final String FLAG_FAVORITE = "flagFavorite"; 652 // Boolean, no attachment = 0, attachment = 1 653 public static final String FLAG_ATTACHMENT = "flagAttachment"; 654 // Bit field for flags which we'll not be selecting on 655 public static final String FLAGS = "flags"; 656 657 // Sync related identifiers 658 // Saved draft info (reusing the never-used "clientId" column) 659 public static final String DRAFT_INFO = "clientId"; 660 // The message-id in the message's header 661 public static final String MESSAGE_ID = "messageId"; 662 663 // References to other Email objects in the database 664 // Foreign key to the Mailbox holding this message [INDEX] 665 // TODO: This column is used in a complicated way: Usually, this refers to the mailbox 666 // the server considers this message to be in. In the case of search results, this key 667 // will refer to a special "search" mailbox, which does not exist on the server. 668 // This is confusing and causes problems, see b/11294681. 669 public static final String MAILBOX_KEY = "mailboxKey"; 670 // Foreign key to the Account holding this message 671 public static final String ACCOUNT_KEY = "accountKey"; 672 673 // Address lists, packed with Address.pack() 674 public static final String FROM_LIST = "fromList"; 675 public static final String TO_LIST = "toList"; 676 public static final String CC_LIST = "ccList"; 677 public static final String BCC_LIST = "bccList"; 678 public static final String REPLY_TO_LIST = "replyToList"; 679 // Meeting invitation related information (for now, start time in ms) 680 public static final String MEETING_INFO = "meetingInfo"; 681 // A text "snippet" derived from the body of the message 682 public static final String SNIPPET = "snippet"; 683 // A column that can be used by sync adapters to store search-related information about 684 // a retrieved message (the messageKey for search results will be a TYPE_SEARCH mailbox 685 // and the sync adapter might, for example, need more information about the original source 686 // of the message) 687 public static final String PROTOCOL_SEARCH_INFO = "protocolSearchInfo"; 688 // Simple thread topic 689 public static final String THREAD_TOPIC = "threadTopic"; 690 // For sync adapter use 691 public static final String SYNC_DATA = "syncData"; 692 693 /** Boolean, unseen = 0, seen = 1 [INDEX] */ 694 public static final String FLAG_SEEN = "flagSeen"; 695 696 // References to other Email objects in the database 697 // Foreign key to the Mailbox holding this message [INDEX] 698 // In cases where mailboxKey is NOT the real mailbox the server considers this message in, 699 // this will be set. See b/11294681 700 // We'd like to get rid of this column when the other changes mentioned in that bug 701 // can be addressed. 702 public static final String MAIN_MAILBOX_KEY = "mainMailboxKey"; 703 704 } 705 706 public static final class Message extends EmailContent { 707 private static final String LOG_TAG = "Email"; 708 709 public static final String TABLE_NAME = "Message"; 710 public static final String UPDATED_TABLE_NAME = "Message_Updates"; 711 public static final String DELETED_TABLE_NAME = "Message_Deletes"; 712 713 // To refer to a specific message, use ContentUris.withAppendedId(CONTENT_URI, id) 714 public static Uri CONTENT_URI; 715 public static Uri CONTENT_URI_LIMIT_1; 716 public static Uri SYNCED_CONTENT_URI; 717 public static Uri SELECTED_MESSAGE_CONTENT_URI ; 718 public static Uri DELETED_CONTENT_URI; 719 public static Uri UPDATED_CONTENT_URI; 720 public static Uri NOTIFIER_URI; 721 initMessage()722 public static void initMessage() { 723 CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/message"); 724 CONTENT_URI_LIMIT_1 = uriWithLimit(CONTENT_URI, 1); 725 SYNCED_CONTENT_URI = 726 Uri.parse(EmailContent.CONTENT_URI + "/syncedMessage"); 727 SELECTED_MESSAGE_CONTENT_URI = 728 Uri.parse(EmailContent.CONTENT_URI + "/messageBySelection"); 729 DELETED_CONTENT_URI = 730 Uri.parse(EmailContent.CONTENT_URI + "/deletedMessage"); 731 UPDATED_CONTENT_URI = 732 Uri.parse(EmailContent.CONTENT_URI + "/updatedMessage"); 733 NOTIFIER_URI = 734 Uri.parse(EmailContent.CONTENT_NOTIFIER_URI + "/message"); 735 } 736 737 public static final int CONTENT_ID_COLUMN = 0; 738 public static final int CONTENT_DISPLAY_NAME_COLUMN = 1; 739 public static final int CONTENT_TIMESTAMP_COLUMN = 2; 740 public static final int CONTENT_SUBJECT_COLUMN = 3; 741 public static final int CONTENT_FLAG_READ_COLUMN = 4; 742 public static final int CONTENT_FLAG_LOADED_COLUMN = 5; 743 public static final int CONTENT_FLAG_FAVORITE_COLUMN = 6; 744 public static final int CONTENT_FLAG_ATTACHMENT_COLUMN = 7; 745 public static final int CONTENT_FLAGS_COLUMN = 8; 746 public static final int CONTENT_SERVER_ID_COLUMN = 9; 747 public static final int CONTENT_DRAFT_INFO_COLUMN = 10; 748 public static final int CONTENT_MESSAGE_ID_COLUMN = 11; 749 public static final int CONTENT_MAILBOX_KEY_COLUMN = 12; 750 public static final int CONTENT_ACCOUNT_KEY_COLUMN = 13; 751 public static final int CONTENT_FROM_LIST_COLUMN = 14; 752 public static final int CONTENT_TO_LIST_COLUMN = 15; 753 public static final int CONTENT_CC_LIST_COLUMN = 16; 754 public static final int CONTENT_BCC_LIST_COLUMN = 17; 755 public static final int CONTENT_REPLY_TO_COLUMN = 18; 756 public static final int CONTENT_SERVER_TIMESTAMP_COLUMN = 19; 757 public static final int CONTENT_MEETING_INFO_COLUMN = 20; 758 public static final int CONTENT_SNIPPET_COLUMN = 21; 759 public static final int CONTENT_PROTOCOL_SEARCH_INFO_COLUMN = 22; 760 public static final int CONTENT_THREAD_TOPIC_COLUMN = 23; 761 public static final int CONTENT_SYNC_DATA_COLUMN = 24; 762 public static final int CONTENT_FLAG_SEEN_COLUMN = 25; 763 public static final int CONTENT_MAIN_MAILBOX_KEY_COLUMN = 26; 764 765 public static final String[] CONTENT_PROJECTION = { 766 MessageColumns._ID, 767 MessageColumns.DISPLAY_NAME, MessageColumns.TIMESTAMP, 768 MessageColumns.SUBJECT, MessageColumns.FLAG_READ, 769 MessageColumns.FLAG_LOADED, MessageColumns.FLAG_FAVORITE, 770 MessageColumns.FLAG_ATTACHMENT, MessageColumns.FLAGS, 771 SyncColumns.SERVER_ID, MessageColumns.DRAFT_INFO, 772 MessageColumns.MESSAGE_ID, MessageColumns.MAILBOX_KEY, 773 MessageColumns.ACCOUNT_KEY, MessageColumns.FROM_LIST, 774 MessageColumns.TO_LIST, MessageColumns.CC_LIST, 775 MessageColumns.BCC_LIST, MessageColumns.REPLY_TO_LIST, 776 SyncColumns.SERVER_TIMESTAMP, MessageColumns.MEETING_INFO, 777 MessageColumns.SNIPPET, MessageColumns.PROTOCOL_SEARCH_INFO, 778 MessageColumns.THREAD_TOPIC, MessageColumns.SYNC_DATA, 779 MessageColumns.FLAG_SEEN, MessageColumns.MAIN_MAILBOX_KEY 780 }; 781 782 public static final int LIST_ID_COLUMN = 0; 783 public static final int LIST_DISPLAY_NAME_COLUMN = 1; 784 public static final int LIST_TIMESTAMP_COLUMN = 2; 785 public static final int LIST_SUBJECT_COLUMN = 3; 786 public static final int LIST_READ_COLUMN = 4; 787 public static final int LIST_LOADED_COLUMN = 5; 788 public static final int LIST_FAVORITE_COLUMN = 6; 789 public static final int LIST_ATTACHMENT_COLUMN = 7; 790 public static final int LIST_FLAGS_COLUMN = 8; 791 public static final int LIST_MAILBOX_KEY_COLUMN = 9; 792 public static final int LIST_ACCOUNT_KEY_COLUMN = 10; 793 public static final int LIST_SERVER_ID_COLUMN = 11; 794 public static final int LIST_SNIPPET_COLUMN = 12; 795 796 // Public projection for common list columns 797 public static final String[] LIST_PROJECTION = { 798 MessageColumns._ID, 799 MessageColumns.DISPLAY_NAME, MessageColumns.TIMESTAMP, 800 MessageColumns.SUBJECT, MessageColumns.FLAG_READ, 801 MessageColumns.FLAG_LOADED, MessageColumns.FLAG_FAVORITE, 802 MessageColumns.FLAG_ATTACHMENT, MessageColumns.FLAGS, 803 MessageColumns.MAILBOX_KEY, MessageColumns.ACCOUNT_KEY, 804 SyncColumns.SERVER_ID, MessageColumns.SNIPPET 805 }; 806 807 public static final int ID_COLUMNS_ID_COLUMN = 0; 808 public static final int ID_COLUMNS_SYNC_SERVER_ID = 1; 809 public static final String[] ID_COLUMNS_PROJECTION = { 810 MessageColumns._ID, SyncColumns.SERVER_ID 811 }; 812 813 public static final String[] ID_COLUMN_PROJECTION = { MessageColumns._ID }; 814 815 public static final String ACCOUNT_KEY_SELECTION = 816 MessageColumns.ACCOUNT_KEY + "=?"; 817 818 public static final String[] MAILBOX_KEY_PROJECTION = { MessageColumns.MAILBOX_KEY }; 819 820 /** 821 * Selection for messages that are loaded 822 * 823 * POP messages at the initial stage have very little information. (Server UID only) 824 * Use this to make sure they're not visible on any UI. 825 * This means unread counts on the mailbox list can be different from the 826 * number of messages in the message list, but it should be transient... 827 */ 828 public static final String FLAG_LOADED_SELECTION = 829 MessageColumns.FLAG_LOADED + " IN (" 830 + Message.FLAG_LOADED_PARTIAL + "," + Message.FLAG_LOADED_COMPLETE 831 + ")"; 832 833 public static final String ALL_FAVORITE_SELECTION = 834 MessageColumns.FLAG_FAVORITE + "=1 AND " 835 + MessageColumns.MAILBOX_KEY + " NOT IN (" 836 + "SELECT " + MailboxColumns._ID + " FROM " + Mailbox.TABLE_NAME + "" 837 + " WHERE " + MailboxColumns.TYPE + " = " + Mailbox.TYPE_TRASH 838 + ")" 839 + " AND " + FLAG_LOADED_SELECTION; 840 841 /** Selection to retrieve all messages in "inbox" for any account */ 842 public static final String ALL_INBOX_SELECTION = 843 MessageColumns.MAILBOX_KEY + " IN (" 844 + "SELECT " + MailboxColumns._ID + " FROM " + Mailbox.TABLE_NAME 845 + " WHERE " + MailboxColumns.TYPE + " = " + Mailbox.TYPE_INBOX 846 + ")" 847 + " AND " + FLAG_LOADED_SELECTION; 848 849 /** Selection to retrieve all messages in "drafts" for any account */ 850 public static final String ALL_DRAFT_SELECTION = 851 MessageColumns.MAILBOX_KEY + " IN (" 852 + "SELECT " + MailboxColumns._ID + " FROM " + Mailbox.TABLE_NAME 853 + " WHERE " + MailboxColumns.TYPE + " = " + Mailbox.TYPE_DRAFTS 854 + ")" 855 + " AND " + FLAG_LOADED_SELECTION; 856 857 /** Selection to retrieve all messages in "outbox" for any account */ 858 public static final String ALL_OUTBOX_SELECTION = 859 MessageColumns.MAILBOX_KEY + " IN (" 860 + "SELECT " + MailboxColumns._ID + " FROM " + Mailbox.TABLE_NAME 861 + " WHERE " + MailboxColumns.TYPE + " = " + Mailbox.TYPE_OUTBOX 862 + ")"; // NOTE No flag_loaded test for outboxes. 863 864 /** Selection to retrieve unread messages in "inbox" for any account */ 865 public static final String ALL_UNREAD_SELECTION = 866 MessageColumns.FLAG_READ + "=0 AND " + ALL_INBOX_SELECTION; 867 868 /** Selection to retrieve unread messages in "inbox" for one account */ 869 public static final String PER_ACCOUNT_UNREAD_SELECTION = 870 ACCOUNT_KEY_SELECTION + " AND " + ALL_UNREAD_SELECTION; 871 872 /** Selection to retrieve all messages in "inbox" for one account */ 873 public static final String PER_ACCOUNT_INBOX_SELECTION = 874 ACCOUNT_KEY_SELECTION + " AND " + ALL_INBOX_SELECTION; 875 876 public static final String PER_ACCOUNT_FAVORITE_SELECTION = 877 ACCOUNT_KEY_SELECTION + " AND " + ALL_FAVORITE_SELECTION; 878 879 public static final String MAILBOX_SELECTION = MessageColumns.MAILBOX_KEY + "=?"; 880 881 // _id field is in AbstractContent 882 public String mDisplayName; 883 public long mTimeStamp; 884 public String mSubject; 885 public boolean mFlagRead = false; 886 public boolean mFlagSeen = false; 887 public int mFlagLoaded = FLAG_LOADED_UNLOADED; 888 public boolean mFlagFavorite = false; 889 public boolean mFlagAttachment = false; 890 public int mFlags = 0; 891 892 public String mServerId; 893 public long mServerTimeStamp; 894 public int mDraftInfo; 895 public String mMessageId; 896 897 public long mMailboxKey; 898 public long mAccountKey; 899 public long mMainMailboxKey; 900 901 public String mFrom; 902 public String mTo; 903 public String mCc; 904 public String mBcc; 905 public String mReplyTo; 906 907 // For now, just the start time of a meeting invite, in ms 908 public String mMeetingInfo; 909 910 public String mSnippet; 911 912 public String mProtocolSearchInfo; 913 914 public String mThreadTopic; 915 916 public String mSyncData; 917 918 /** 919 * Base64-encoded representation of the byte array provided by servers for identifying 920 * messages belonging to the same conversation thread. Currently unsupported and not 921 * persisted in the database. 922 */ 923 public String mServerConversationId; 924 925 // The following transient members may be used while building and manipulating messages, 926 // but they are NOT persisted directly by EmailProvider. See Body for related fields. 927 transient public String mText; 928 transient public String mHtml; 929 transient public long mSourceKey; 930 transient public ArrayList<Attachment> mAttachments = null; 931 transient public int mQuotedTextStartPos; 932 933 934 // Values used in mFlagRead 935 public static final int UNREAD = 0; 936 public static final int READ = 1; 937 938 // Values used in mFlagLoaded 939 public static final int FLAG_LOADED_UNLOADED = 0; 940 public static final int FLAG_LOADED_COMPLETE = 1; 941 public static final int FLAG_LOADED_PARTIAL = 2; 942 public static final int FLAG_LOADED_DELETED = 3; 943 public static final int FLAG_LOADED_UNKNOWN = 4; 944 945 // Bits used in mFlags 946 // The following three states are mutually exclusive, and indicate whether the message is an 947 // original, a reply, or a forward 948 public static final int FLAG_TYPE_REPLY = 1<<0; 949 public static final int FLAG_TYPE_FORWARD = 1<<1; 950 public static final int FLAG_TYPE_MASK = FLAG_TYPE_REPLY | FLAG_TYPE_FORWARD; 951 // The following flags indicate messages that are determined to be incoming meeting related 952 // (e.g. invites from others) 953 public static final int FLAG_INCOMING_MEETING_INVITE = 1<<2; 954 public static final int FLAG_INCOMING_MEETING_CANCEL = 1<<3; 955 public static final int FLAG_INCOMING_MEETING_MASK = 956 FLAG_INCOMING_MEETING_INVITE | FLAG_INCOMING_MEETING_CANCEL; 957 // The following flags indicate messages that are outgoing and meeting related 958 // (e.g. invites TO others) 959 public static final int FLAG_OUTGOING_MEETING_INVITE = 1<<4; 960 public static final int FLAG_OUTGOING_MEETING_CANCEL = 1<<5; 961 public static final int FLAG_OUTGOING_MEETING_ACCEPT = 1<<6; 962 public static final int FLAG_OUTGOING_MEETING_DECLINE = 1<<7; 963 public static final int FLAG_OUTGOING_MEETING_TENTATIVE = 1<<8; 964 public static final int FLAG_OUTGOING_MEETING_MASK = 965 FLAG_OUTGOING_MEETING_INVITE | FLAG_OUTGOING_MEETING_CANCEL | 966 FLAG_OUTGOING_MEETING_ACCEPT | FLAG_OUTGOING_MEETING_DECLINE | 967 FLAG_OUTGOING_MEETING_TENTATIVE; 968 public static final int FLAG_OUTGOING_MEETING_REQUEST_MASK = 969 FLAG_OUTGOING_MEETING_INVITE | FLAG_OUTGOING_MEETING_CANCEL; 970 // 8 general purpose flags (bits) that may be used at the discretion of the sync adapter 971 public static final int FLAG_SYNC_ADAPTER_SHIFT = 9; 972 public static final int FLAG_SYNC_ADAPTER_MASK = 255 << FLAG_SYNC_ADAPTER_SHIFT; 973 /** If set, the outgoing message should *not* include the quoted original message. */ 974 public static final int FLAG_NOT_INCLUDE_QUOTED_TEXT = 1 << 17; 975 public static final int FLAG_REPLIED_TO = 1 << 18; 976 public static final int FLAG_FORWARDED = 1 << 19; 977 978 // Outgoing, original message 979 public static final int FLAG_TYPE_ORIGINAL = 1 << 20; 980 // Outgoing, reply all message; note, FLAG_TYPE_REPLY should also be set for backward 981 // compatibility 982 public static final int FLAG_TYPE_REPLY_ALL = 1 << 21; 983 984 // Flag used in draftInfo to indicate that the reference message should be appended 985 public static final int DRAFT_INFO_APPEND_REF_MESSAGE = 1 << 24; 986 public static final int DRAFT_INFO_QUOTE_POS_MASK = 0xFFFFFF; 987 988 /** a pseudo ID for "no message". */ 989 public static final long NO_MESSAGE = -1L; 990 991 private static final int ATTACHMENT_INDEX_OFFSET = 2; 992 Message()993 public Message() { 994 mBaseUri = CONTENT_URI; 995 } 996 997 @Override toContentValues()998 public ContentValues toContentValues() { 999 ContentValues values = new ContentValues(); 1000 1001 // Assign values for each row. 1002 values.put(MessageColumns.DISPLAY_NAME, mDisplayName); 1003 values.put(MessageColumns.TIMESTAMP, mTimeStamp); 1004 values.put(MessageColumns.SUBJECT, mSubject); 1005 values.put(MessageColumns.FLAG_READ, mFlagRead); 1006 values.put(MessageColumns.FLAG_SEEN, mFlagSeen); 1007 values.put(MessageColumns.FLAG_LOADED, mFlagLoaded); 1008 values.put(MessageColumns.FLAG_FAVORITE, mFlagFavorite); 1009 values.put(MessageColumns.FLAG_ATTACHMENT, mFlagAttachment); 1010 values.put(MessageColumns.FLAGS, mFlags); 1011 values.put(SyncColumns.SERVER_ID, mServerId); 1012 values.put(SyncColumns.SERVER_TIMESTAMP, mServerTimeStamp); 1013 values.put(MessageColumns.DRAFT_INFO, mDraftInfo); 1014 values.put(MessageColumns.MESSAGE_ID, mMessageId); 1015 values.put(MessageColumns.MAILBOX_KEY, mMailboxKey); 1016 values.put(MessageColumns.ACCOUNT_KEY, mAccountKey); 1017 values.put(MessageColumns.FROM_LIST, mFrom); 1018 values.put(MessageColumns.TO_LIST, mTo); 1019 values.put(MessageColumns.CC_LIST, mCc); 1020 values.put(MessageColumns.BCC_LIST, mBcc); 1021 values.put(MessageColumns.REPLY_TO_LIST, mReplyTo); 1022 values.put(MessageColumns.MEETING_INFO, mMeetingInfo); 1023 values.put(MessageColumns.SNIPPET, mSnippet); 1024 values.put(MessageColumns.PROTOCOL_SEARCH_INFO, mProtocolSearchInfo); 1025 values.put(MessageColumns.THREAD_TOPIC, mThreadTopic); 1026 values.put(MessageColumns.SYNC_DATA, mSyncData); 1027 values.put(MessageColumns.MAIN_MAILBOX_KEY, mMainMailboxKey); 1028 return values; 1029 } 1030 restoreMessageWithId(Context context, long id)1031 public static Message restoreMessageWithId(Context context, long id) { 1032 return EmailContent.restoreContentWithId(context, Message.class, 1033 Message.CONTENT_URI, Message.CONTENT_PROJECTION, id); 1034 } 1035 1036 @Override restore(Cursor cursor)1037 public void restore(Cursor cursor) { 1038 mBaseUri = CONTENT_URI; 1039 mId = cursor.getLong(CONTENT_ID_COLUMN); 1040 mDisplayName = cursor.getString(CONTENT_DISPLAY_NAME_COLUMN); 1041 mTimeStamp = cursor.getLong(CONTENT_TIMESTAMP_COLUMN); 1042 mSubject = cursor.getString(CONTENT_SUBJECT_COLUMN); 1043 mFlagRead = cursor.getInt(CONTENT_FLAG_READ_COLUMN) == 1; 1044 mFlagSeen = cursor.getInt(CONTENT_FLAG_SEEN_COLUMN) == 1; 1045 mFlagLoaded = cursor.getInt(CONTENT_FLAG_LOADED_COLUMN); 1046 mFlagFavorite = cursor.getInt(CONTENT_FLAG_FAVORITE_COLUMN) == 1; 1047 mFlagAttachment = cursor.getInt(CONTENT_FLAG_ATTACHMENT_COLUMN) == 1; 1048 mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN); 1049 mServerId = cursor.getString(CONTENT_SERVER_ID_COLUMN); 1050 mServerTimeStamp = cursor.getLong(CONTENT_SERVER_TIMESTAMP_COLUMN); 1051 mDraftInfo = cursor.getInt(CONTENT_DRAFT_INFO_COLUMN); 1052 mMessageId = cursor.getString(CONTENT_MESSAGE_ID_COLUMN); 1053 mMailboxKey = cursor.getLong(CONTENT_MAILBOX_KEY_COLUMN); 1054 mMainMailboxKey = cursor.getLong(CONTENT_MAIN_MAILBOX_KEY_COLUMN); 1055 mAccountKey = cursor.getLong(CONTENT_ACCOUNT_KEY_COLUMN); 1056 mFrom = cursor.getString(CONTENT_FROM_LIST_COLUMN); 1057 mTo = cursor.getString(CONTENT_TO_LIST_COLUMN); 1058 mCc = cursor.getString(CONTENT_CC_LIST_COLUMN); 1059 mBcc = cursor.getString(CONTENT_BCC_LIST_COLUMN); 1060 mReplyTo = cursor.getString(CONTENT_REPLY_TO_COLUMN); 1061 mMeetingInfo = cursor.getString(CONTENT_MEETING_INFO_COLUMN); 1062 mSnippet = cursor.getString(CONTENT_SNIPPET_COLUMN); 1063 mProtocolSearchInfo = cursor.getString(CONTENT_PROTOCOL_SEARCH_INFO_COLUMN); 1064 mThreadTopic = cursor.getString(CONTENT_THREAD_TOPIC_COLUMN); 1065 mSyncData = cursor.getString(CONTENT_SYNC_DATA_COLUMN); 1066 } 1067 1068 /* 1069 * Override this so that we can store the Body first and link it to the Message 1070 * Also, attachments when we get there... 1071 * (non-Javadoc) 1072 * @see com.android.email.provider.EmailContent#save(android.content.Context) 1073 */ 1074 @Override save(Context context)1075 public Uri save(Context context) { 1076 1077 boolean doSave = !isSaved(); 1078 1079 // This logic is in place so I can (a) short circuit the expensive stuff when 1080 // possible, and (b) override (and throw) if anyone tries to call save() or update() 1081 // directly for Message, which are unsupported. 1082 if (mText == null && mHtml == null && 1083 (mAttachments == null || mAttachments.isEmpty())) { 1084 if (doSave) { 1085 return super.save(context); 1086 } else { 1087 // FLAG: Should we be doing this? In the base class, if someone calls "save" on 1088 // an EmailContent that is already saved, it throws an exception. 1089 // Call update, rather than super.update in case we ever override it 1090 if (update(context, toContentValues()) == 1) { 1091 return getUri(); 1092 } 1093 return null; 1094 } 1095 } 1096 1097 final ArrayList<ContentProviderOperation> ops = 1098 new ArrayList<ContentProviderOperation>(); 1099 addSaveOps(ops); 1100 try { 1101 final ContentProviderResult[] results = 1102 context.getContentResolver().applyBatch(AUTHORITY, ops); 1103 // If saving, set the mId's of the various saved objects 1104 if (doSave) { 1105 Uri u = results[0].uri; 1106 mId = Long.parseLong(u.getPathSegments().get(1)); 1107 if (mAttachments != null) { 1108 // Skip over the first two items in the result array 1109 for (int i = 0; i < mAttachments.size(); i++) { 1110 final Attachment a = mAttachments.get(i); 1111 1112 final int resultIndex = i + ATTACHMENT_INDEX_OFFSET; 1113 // Save the id of the attachment record 1114 if (resultIndex < results.length) { 1115 u = results[resultIndex].uri; 1116 } else { 1117 // We didn't find the expected attachment, log this error 1118 LogUtils.e(LOG_TAG, "Invalid index into ContentProviderResults: " + 1119 resultIndex); 1120 u = null; 1121 } 1122 if (u != null) { 1123 a.mId = Long.parseLong(u.getPathSegments().get(1)); 1124 } 1125 a.mMessageKey = mId; 1126 } 1127 } 1128 return u; 1129 } else { 1130 return null; 1131 } 1132 } catch (RemoteException e) { 1133 // There is nothing to be done here; fail by returning null 1134 } catch (OperationApplicationException e) { 1135 // There is nothing to be done here; fail by returning null 1136 } 1137 return null; 1138 } 1139 1140 /** 1141 * Save or update a message 1142 * @param ops an array of CPOs that we'll add to 1143 */ addSaveOps(ArrayList<ContentProviderOperation> ops)1144 public void addSaveOps(ArrayList<ContentProviderOperation> ops) { 1145 boolean isNew = !isSaved(); 1146 ContentProviderOperation.Builder b; 1147 // First, save/update the message 1148 if (isNew) { 1149 b = ContentProviderOperation.newInsert(mBaseUri); 1150 } else { 1151 b = ContentProviderOperation.newUpdate(mBaseUri) 1152 .withSelection(MessageColumns._ID + "=?", 1153 new String[] {Long.toString(mId)}); 1154 } 1155 // Generate the snippet here, before we create the CPO for Message 1156 if (mText != null) { 1157 mSnippet = TextUtilities.makeSnippetFromPlainText(mText); 1158 } else if (mHtml != null) { 1159 mSnippet = TextUtilities.makeSnippetFromHtmlText(mHtml); 1160 } 1161 ops.add(b.withValues(toContentValues()).build()); 1162 1163 // Create and save the body 1164 ContentValues cv = new ContentValues(); 1165 if (mText != null) { 1166 cv.put(BodyColumns.TEXT_CONTENT, mText); 1167 } 1168 if (mHtml != null) { 1169 cv.put(BodyColumns.HTML_CONTENT, mHtml); 1170 } 1171 if (mSourceKey != 0) { 1172 cv.put(BodyColumns.SOURCE_MESSAGE_KEY, mSourceKey); 1173 } 1174 if (mQuotedTextStartPos != 0) { 1175 cv.put(BodyColumns.QUOTED_TEXT_START_POS, mQuotedTextStartPos); 1176 } 1177 // We'll need this if we're new 1178 int messageBackValue = ops.size() - 1; 1179 // Only create a body if we've got some data 1180 if (!cv.keySet().isEmpty()) { 1181 b = ContentProviderOperation.newInsert(Body.CONTENT_URI); 1182 // Put our message id in the Body 1183 if (!isNew) { 1184 cv.put(BodyColumns.MESSAGE_KEY, mId); 1185 } 1186 b.withValues(cv); 1187 // If we're new, create a back value entry 1188 if (isNew) { 1189 ContentValues backValues = new ContentValues(); 1190 backValues.put(BodyColumns.MESSAGE_KEY, messageBackValue); 1191 b.withValueBackReferences(backValues); 1192 } 1193 // And add the Body operation 1194 ops.add(b.build()); 1195 } 1196 1197 // Create the attaachments, if any 1198 if (mAttachments != null) { 1199 for (Attachment att: mAttachments) { 1200 if (!isNew) { 1201 att.mMessageKey = mId; 1202 } 1203 b = ContentProviderOperation.newInsert(Attachment.CONTENT_URI) 1204 .withValues(att.toContentValues()); 1205 if (isNew) { 1206 b.withValueBackReference(AttachmentColumns.MESSAGE_KEY, messageBackValue); 1207 } 1208 ops.add(b.build()); 1209 } 1210 } 1211 } 1212 1213 /** 1214 * @return number of favorite (starred) messages throughout all accounts. 1215 */ getFavoriteMessageCount(Context context)1216 public static int getFavoriteMessageCount(Context context) { 1217 return count(context, Message.CONTENT_URI, ALL_FAVORITE_SELECTION, null); 1218 } 1219 1220 /** 1221 * @return number of favorite (starred) messages for an account 1222 */ getFavoriteMessageCount(Context context, long accountId)1223 public static int getFavoriteMessageCount(Context context, long accountId) { 1224 return count(context, Message.CONTENT_URI, PER_ACCOUNT_FAVORITE_SELECTION, 1225 new String[]{Long.toString(accountId)}); 1226 } 1227 getKeyColumnLong(Context context, long messageId, String column)1228 public static long getKeyColumnLong(Context context, long messageId, String column) { 1229 String[] columns = 1230 Utility.getRowColumns(context, Message.CONTENT_URI, messageId, column); 1231 if (columns != null && columns[0] != null) { 1232 return Long.parseLong(columns[0]); 1233 } 1234 return -1; 1235 } 1236 1237 /** 1238 * Returns the where clause for a message list selection. 1239 * 1240 * Accesses the detabase to determine the mailbox type. DO NOT CALL FROM UI THREAD. 1241 */ buildMessageListSelection( Context context, long accountId, long mailboxId)1242 public static String buildMessageListSelection( 1243 Context context, long accountId, long mailboxId) { 1244 1245 if (mailboxId == Mailbox.QUERY_ALL_INBOXES) { 1246 return Message.ALL_INBOX_SELECTION; 1247 } 1248 if (mailboxId == Mailbox.QUERY_ALL_DRAFTS) { 1249 return Message.ALL_DRAFT_SELECTION; 1250 } 1251 if (mailboxId == Mailbox.QUERY_ALL_OUTBOX) { 1252 return Message.ALL_OUTBOX_SELECTION; 1253 } 1254 if (mailboxId == Mailbox.QUERY_ALL_UNREAD) { 1255 return Message.ALL_UNREAD_SELECTION; 1256 } 1257 // TODO: we only support per-account starred mailbox right now, but presumably, we 1258 // can surface the same thing for unread. 1259 if (mailboxId == Mailbox.QUERY_ALL_FAVORITES) { 1260 if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) { 1261 return Message.ALL_FAVORITE_SELECTION; 1262 } 1263 1264 final StringBuilder selection = new StringBuilder(); 1265 selection.append(MessageColumns.ACCOUNT_KEY).append('=').append(accountId) 1266 .append(" AND ") 1267 .append(Message.ALL_FAVORITE_SELECTION); 1268 return selection.toString(); 1269 } 1270 1271 // Now it's a regular mailbox. 1272 final StringBuilder selection = new StringBuilder(); 1273 1274 selection.append(MessageColumns.MAILBOX_KEY).append('=').append(mailboxId); 1275 1276 if (Mailbox.getMailboxType(context, mailboxId) != Mailbox.TYPE_OUTBOX) { 1277 selection.append(" AND ").append(Message.FLAG_LOADED_SELECTION); 1278 } 1279 return selection.toString(); 1280 } 1281 setFlags(boolean quotedReply, boolean quotedForward)1282 public void setFlags(boolean quotedReply, boolean quotedForward) { 1283 // Set message flags as well 1284 if (quotedReply || quotedForward) { 1285 mFlags &= ~Message.FLAG_TYPE_MASK; 1286 mFlags |= quotedReply 1287 ? Message.FLAG_TYPE_REPLY 1288 : Message.FLAG_TYPE_FORWARD; 1289 } 1290 } 1291 } 1292 1293 public interface AttachmentColumns extends BaseColumns { 1294 // The display name of the attachment 1295 public static final String FILENAME = "fileName"; 1296 // The mime type of the attachment 1297 public static final String MIME_TYPE = "mimeType"; 1298 // The size of the attachment in bytes 1299 public static final String SIZE = "size"; 1300 // The (internal) contentId of the attachment (inline attachments will have these) 1301 public static final String CONTENT_ID = "contentId"; 1302 // The location of the loaded attachment (probably a file) 1303 @SuppressWarnings("hiding") 1304 public static final String CONTENT_URI = "contentUri"; 1305 // The cached location of the attachment 1306 public static final String CACHED_FILE = "cachedFile"; 1307 // A foreign key into the Message table (the message owning this attachment) 1308 public static final String MESSAGE_KEY = "messageKey"; 1309 // The location of the attachment on the server side 1310 // For IMAP, this is a part number (e.g. 2.1); for EAS, it's the internal file name 1311 public static final String LOCATION = "location"; 1312 // The transfer encoding of the attachment 1313 public static final String ENCODING = "encoding"; 1314 // Not currently used 1315 public static final String CONTENT = "content"; 1316 // Flags 1317 public static final String FLAGS = "flags"; 1318 // Content that is actually contained in the Attachment row 1319 public static final String CONTENT_BYTES = "content_bytes"; 1320 // A foreign key into the Account table (for the message owning this attachment) 1321 public static final String ACCOUNT_KEY = "accountKey"; 1322 // The UIProvider state of the attachment 1323 public static final String UI_STATE = "uiState"; 1324 // The UIProvider destination of the attachment 1325 public static final String UI_DESTINATION = "uiDestination"; 1326 // The UIProvider downloaded size of the attachment 1327 public static final String UI_DOWNLOADED_SIZE = "uiDownloadedSize"; 1328 } 1329 1330 public static final class Attachment extends EmailContent implements Parcelable { 1331 public static final String TABLE_NAME = "Attachment"; 1332 public static final String ATTACHMENT_PROVIDER_LEGACY_URI_PREFIX = 1333 "content://com.android.email.attachmentprovider"; 1334 1335 public static final String CACHED_FILE_QUERY_PARAM = "filePath"; 1336 1337 public static Uri CONTENT_URI; 1338 // This must be used with an appended id: ContentUris.withAppendedId(MESSAGE_ID_URI, id) 1339 public static Uri MESSAGE_ID_URI; 1340 public static String ATTACHMENT_PROVIDER_URI_PREFIX; 1341 public static String ATTACHMENT_PROVIDER_AUTHORITY; 1342 public static boolean sUsingLegacyPrefix; 1343 initAttachment()1344 public static void initAttachment() { 1345 CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/attachment"); 1346 MESSAGE_ID_URI = Uri.parse( 1347 EmailContent.CONTENT_URI + "/attachment/message"); 1348 ATTACHMENT_PROVIDER_AUTHORITY = EmailContent.EMAIL_PACKAGE_NAME + 1349 ".attachmentprovider"; 1350 ATTACHMENT_PROVIDER_URI_PREFIX = "content://" + ATTACHMENT_PROVIDER_AUTHORITY; 1351 sUsingLegacyPrefix = 1352 ATTACHMENT_PROVIDER_URI_PREFIX.equals(ATTACHMENT_PROVIDER_LEGACY_URI_PREFIX); 1353 } 1354 1355 public String mFileName; 1356 public String mMimeType; 1357 public long mSize; 1358 public String mContentId; 1359 private String mContentUri; 1360 private String mCachedFileUri; 1361 public long mMessageKey; 1362 public String mLocation; 1363 public String mEncoding; 1364 public String mContent; // Not currently used 1365 public int mFlags; 1366 public byte[] mContentBytes; 1367 public long mAccountKey; 1368 public int mUiState; 1369 public int mUiDestination; 1370 public int mUiDownloadedSize; 1371 1372 public static final int CONTENT_ID_COLUMN = 0; 1373 public static final int CONTENT_FILENAME_COLUMN = 1; 1374 public static final int CONTENT_MIME_TYPE_COLUMN = 2; 1375 public static final int CONTENT_SIZE_COLUMN = 3; 1376 public static final int CONTENT_CONTENT_ID_COLUMN = 4; 1377 public static final int CONTENT_CONTENT_URI_COLUMN = 5; 1378 public static final int CONTENT_CACHED_FILE_COLUMN = 6; 1379 public static final int CONTENT_MESSAGE_ID_COLUMN = 7; 1380 public static final int CONTENT_LOCATION_COLUMN = 8; 1381 public static final int CONTENT_ENCODING_COLUMN = 9; 1382 public static final int CONTENT_CONTENT_COLUMN = 10; // Not currently used 1383 public static final int CONTENT_FLAGS_COLUMN = 11; 1384 public static final int CONTENT_CONTENT_BYTES_COLUMN = 12; 1385 public static final int CONTENT_ACCOUNT_KEY_COLUMN = 13; 1386 public static final int CONTENT_UI_STATE_COLUMN = 14; 1387 public static final int CONTENT_UI_DESTINATION_COLUMN = 15; 1388 public static final int CONTENT_UI_DOWNLOADED_SIZE_COLUMN = 16; 1389 public static final String[] CONTENT_PROJECTION = { 1390 AttachmentColumns._ID, AttachmentColumns.FILENAME, AttachmentColumns.MIME_TYPE, 1391 AttachmentColumns.SIZE, AttachmentColumns.CONTENT_ID, AttachmentColumns.CONTENT_URI, 1392 AttachmentColumns.CACHED_FILE, AttachmentColumns.MESSAGE_KEY, 1393 AttachmentColumns.LOCATION, AttachmentColumns.ENCODING, AttachmentColumns.CONTENT, 1394 AttachmentColumns.FLAGS, AttachmentColumns.CONTENT_BYTES, AttachmentColumns.ACCOUNT_KEY, 1395 AttachmentColumns.UI_STATE, AttachmentColumns.UI_DESTINATION, 1396 AttachmentColumns.UI_DOWNLOADED_SIZE 1397 }; 1398 1399 // All attachments with an empty URI, regardless of mailbox 1400 public static final String PRECACHE_SELECTION = 1401 AttachmentColumns.CONTENT_URI + " isnull AND " + AttachmentColumns.FLAGS + "=0"; 1402 // Attachments with an empty URI that are in an inbox 1403 public static final String PRECACHE_INBOX_SELECTION = 1404 PRECACHE_SELECTION + " AND " + AttachmentColumns.MESSAGE_KEY + " IN (" 1405 + "SELECT " + MessageColumns._ID + " FROM " + Message.TABLE_NAME 1406 + " WHERE " + Message.ALL_INBOX_SELECTION 1407 + ")"; 1408 1409 // Bits used in mFlags 1410 // WARNING: AttachmentService relies on the fact that ALL of the flags below 1411 // disqualify attachments for precaching. If you add a flag that does NOT disqualify an 1412 // attachment for precaching, you MUST change the PRECACHE_SELECTION definition above 1413 1414 // Instruct Rfc822Output to 1) not use Content-Disposition and 2) use multipart/alternative 1415 // with this attachment. This is only valid if there is one and only one attachment and 1416 // that attachment has this flag set 1417 public static final int FLAG_ICS_ALTERNATIVE_PART = 1<<0; 1418 // Indicate that this attachment has been requested for downloading by the user; this is 1419 // the highest priority for attachment downloading 1420 public static final int FLAG_DOWNLOAD_USER_REQUEST = 1<<1; 1421 // Indicate that this attachment needs to be downloaded as part of an outgoing forwarded 1422 // message 1423 public static final int FLAG_DOWNLOAD_FORWARD = 1<<2; 1424 // Indicates that the attachment download failed in a non-recoverable manner 1425 public static final int FLAG_DOWNLOAD_FAILED = 1<<3; 1426 // Allow "room" for some additional download-related flags here 1427 // Indicates that the attachment will be smart-forwarded 1428 public static final int FLAG_SMART_FORWARD = 1<<8; 1429 // Indicates that the attachment cannot be forwarded due to a policy restriction 1430 public static final int FLAG_POLICY_DISALLOWS_DOWNLOAD = 1<<9; 1431 // Indicates that this is a dummy placeholder attachment. 1432 public static final int FLAG_DUMMY_ATTACHMENT = 1<<10; 1433 1434 /** 1435 * no public constructor since this is a utility class 1436 */ Attachment()1437 public Attachment() { 1438 mBaseUri = CONTENT_URI; 1439 } 1440 setCachedFileUri(String cachedFile)1441 public void setCachedFileUri(String cachedFile) { 1442 mCachedFileUri = cachedFile; 1443 } 1444 getCachedFileUri()1445 public String getCachedFileUri() { 1446 return mCachedFileUri; 1447 } 1448 setContentUri(String contentUri)1449 public void setContentUri(String contentUri) { 1450 mContentUri = contentUri; 1451 } 1452 getContentUri()1453 public String getContentUri() { 1454 if (mContentUri == null) return null; // 1455 // If we're not using the legacy prefix and the uri IS, we need to modify it 1456 if (!Attachment.sUsingLegacyPrefix && 1457 mContentUri.startsWith(Attachment.ATTACHMENT_PROVIDER_LEGACY_URI_PREFIX)) { 1458 // In an upgrade scenario, we may still have legacy attachment Uri's 1459 // Skip past content:// 1460 int prefix = mContentUri.indexOf('/', 10); 1461 if (prefix > 0) { 1462 // Create a proper uri string using the actual provider 1463 return ATTACHMENT_PROVIDER_URI_PREFIX + "/" + mContentUri.substring(prefix); 1464 } else { 1465 LogUtils.e("Attachment", "Improper contentUri format: " + mContentUri); 1466 // Belt & suspenders; can't really happen 1467 return mContentUri; 1468 } 1469 } else { 1470 return mContentUri; 1471 } 1472 } 1473 1474 /** 1475 * Restore an Attachment from the database, given its unique id 1476 * @param context 1477 * @param id 1478 * @return the instantiated Attachment 1479 */ restoreAttachmentWithId(Context context, long id)1480 public static Attachment restoreAttachmentWithId(Context context, long id) { 1481 return EmailContent.restoreContentWithId(context, Attachment.class, 1482 Attachment.CONTENT_URI, Attachment.CONTENT_PROJECTION, id); 1483 } 1484 1485 /** 1486 * Restore all the Attachments of a message given its messageId 1487 */ restoreAttachmentsWithMessageId(Context context, long messageId)1488 public static Attachment[] restoreAttachmentsWithMessageId(Context context, 1489 long messageId) { 1490 Uri uri = ContentUris.withAppendedId(MESSAGE_ID_URI, messageId); 1491 Cursor c = context.getContentResolver().query(uri, CONTENT_PROJECTION, 1492 null, null, null); 1493 try { 1494 int count = c.getCount(); 1495 Attachment[] attachments = new Attachment[count]; 1496 for (int i = 0; i < count; ++i) { 1497 c.moveToNext(); 1498 Attachment attach = new Attachment(); 1499 attach.restore(c); 1500 attachments[i] = attach; 1501 } 1502 return attachments; 1503 } finally { 1504 c.close(); 1505 } 1506 } 1507 1508 /** 1509 * Creates a unique file in the external store by appending a hyphen 1510 * and a number to the given filename. 1511 * @param filename 1512 * @return a new File object, or null if one could not be created 1513 */ createUniqueFile(String filename)1514 public static File createUniqueFile(String filename) { 1515 // TODO Handle internal storage, as required 1516 if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { 1517 File directory = Environment.getExternalStorageDirectory(); 1518 File file = new File(directory, filename); 1519 if (!file.exists()) { 1520 return file; 1521 } 1522 // Get the extension of the file, if any. 1523 int index = filename.lastIndexOf('.'); 1524 String name = filename; 1525 String extension = ""; 1526 if (index != -1) { 1527 name = filename.substring(0, index); 1528 extension = filename.substring(index); 1529 } 1530 for (int i = 2; i < Integer.MAX_VALUE; i++) { 1531 file = new File(directory, name + '-' + i + extension); 1532 if (!file.exists()) { 1533 return file; 1534 } 1535 } 1536 return null; 1537 } 1538 return null; 1539 } 1540 1541 @Override restore(Cursor cursor)1542 public void restore(Cursor cursor) { 1543 mBaseUri = CONTENT_URI; 1544 mId = cursor.getLong(CONTENT_ID_COLUMN); 1545 mFileName= cursor.getString(CONTENT_FILENAME_COLUMN); 1546 mMimeType = cursor.getString(CONTENT_MIME_TYPE_COLUMN); 1547 mSize = cursor.getLong(CONTENT_SIZE_COLUMN); 1548 mContentId = cursor.getString(CONTENT_CONTENT_ID_COLUMN); 1549 mContentUri = cursor.getString(CONTENT_CONTENT_URI_COLUMN); 1550 mCachedFileUri = cursor.getString(CONTENT_CACHED_FILE_COLUMN); 1551 mMessageKey = cursor.getLong(CONTENT_MESSAGE_ID_COLUMN); 1552 mLocation = cursor.getString(CONTENT_LOCATION_COLUMN); 1553 mEncoding = cursor.getString(CONTENT_ENCODING_COLUMN); 1554 mContent = cursor.getString(CONTENT_CONTENT_COLUMN); 1555 mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN); 1556 mContentBytes = cursor.getBlob(CONTENT_CONTENT_BYTES_COLUMN); 1557 mAccountKey = cursor.getLong(CONTENT_ACCOUNT_KEY_COLUMN); 1558 mUiState = cursor.getInt(CONTENT_UI_STATE_COLUMN); 1559 mUiDestination = cursor.getInt(CONTENT_UI_DESTINATION_COLUMN); 1560 mUiDownloadedSize = cursor.getInt(CONTENT_UI_DOWNLOADED_SIZE_COLUMN); 1561 } 1562 1563 @Override toContentValues()1564 public ContentValues toContentValues() { 1565 ContentValues values = new ContentValues(); 1566 values.put(AttachmentColumns.FILENAME, mFileName); 1567 values.put(AttachmentColumns.MIME_TYPE, mMimeType); 1568 values.put(AttachmentColumns.SIZE, mSize); 1569 values.put(AttachmentColumns.CONTENT_ID, mContentId); 1570 values.put(AttachmentColumns.CONTENT_URI, mContentUri); 1571 values.put(AttachmentColumns.CACHED_FILE, mCachedFileUri); 1572 values.put(AttachmentColumns.MESSAGE_KEY, mMessageKey); 1573 values.put(AttachmentColumns.LOCATION, mLocation); 1574 values.put(AttachmentColumns.ENCODING, mEncoding); 1575 values.put(AttachmentColumns.CONTENT, mContent); 1576 values.put(AttachmentColumns.FLAGS, mFlags); 1577 values.put(AttachmentColumns.CONTENT_BYTES, mContentBytes); 1578 values.put(AttachmentColumns.ACCOUNT_KEY, mAccountKey); 1579 values.put(AttachmentColumns.UI_STATE, mUiState); 1580 values.put(AttachmentColumns.UI_DESTINATION, mUiDestination); 1581 values.put(AttachmentColumns.UI_DOWNLOADED_SIZE, mUiDownloadedSize); 1582 return values; 1583 } 1584 1585 @Override describeContents()1586 public int describeContents() { 1587 return 0; 1588 } 1589 1590 @Override writeToParcel(Parcel dest, int flags)1591 public void writeToParcel(Parcel dest, int flags) { 1592 // mBaseUri is not parceled 1593 dest.writeLong(mId); 1594 dest.writeString(mFileName); 1595 dest.writeString(mMimeType); 1596 dest.writeLong(mSize); 1597 dest.writeString(mContentId); 1598 dest.writeString(mContentUri); 1599 dest.writeString(mCachedFileUri); 1600 dest.writeLong(mMessageKey); 1601 dest.writeString(mLocation); 1602 dest.writeString(mEncoding); 1603 dest.writeString(mContent); 1604 dest.writeInt(mFlags); 1605 dest.writeLong(mAccountKey); 1606 if (mContentBytes == null) { 1607 dest.writeInt(-1); 1608 } else { 1609 dest.writeInt(mContentBytes.length); 1610 dest.writeByteArray(mContentBytes); 1611 } 1612 dest.writeInt(mUiState); 1613 dest.writeInt(mUiDestination); 1614 dest.writeInt(mUiDownloadedSize); 1615 } 1616 Attachment(Parcel in)1617 public Attachment(Parcel in) { 1618 mBaseUri = Attachment.CONTENT_URI; 1619 mId = in.readLong(); 1620 mFileName = in.readString(); 1621 mMimeType = in.readString(); 1622 mSize = in.readLong(); 1623 mContentId = in.readString(); 1624 mContentUri = in.readString(); 1625 mCachedFileUri = in.readString(); 1626 mMessageKey = in.readLong(); 1627 mLocation = in.readString(); 1628 mEncoding = in.readString(); 1629 mContent = in.readString(); 1630 mFlags = in.readInt(); 1631 mAccountKey = in.readLong(); 1632 final int contentBytesLen = in.readInt(); 1633 if (contentBytesLen == -1) { 1634 mContentBytes = null; 1635 } else { 1636 mContentBytes = new byte[contentBytesLen]; 1637 in.readByteArray(mContentBytes); 1638 } 1639 mUiState = in.readInt(); 1640 mUiDestination = in.readInt(); 1641 mUiDownloadedSize = in.readInt(); 1642 } 1643 1644 public static final Parcelable.Creator<EmailContent.Attachment> CREATOR 1645 = new Parcelable.Creator<EmailContent.Attachment>() { 1646 @Override 1647 public EmailContent.Attachment createFromParcel(Parcel in) { 1648 return new EmailContent.Attachment(in); 1649 } 1650 1651 @Override 1652 public EmailContent.Attachment[] newArray(int size) { 1653 return new EmailContent.Attachment[size]; 1654 } 1655 }; 1656 1657 @Override toString()1658 public String toString() { 1659 return "[" + mFileName + ", " + mMimeType + ", " + mSize + ", " + mContentId + ", " 1660 + mContentUri + ", " + mCachedFileUri + ", " + mMessageKey + ", " 1661 + mLocation + ", " + mEncoding + ", " + mFlags + ", " + mContentBytes + ", " 1662 + mAccountKey + "," + mUiState + "," + mUiDestination + "," 1663 + mUiDownloadedSize + "]"; 1664 } 1665 } 1666 1667 public interface AccountColumns extends BaseColumns { 1668 // The display name of the account (user-settable) 1669 public static final String DISPLAY_NAME = "displayName"; 1670 // The email address corresponding to this account 1671 public static final String EMAIL_ADDRESS = "emailAddress"; 1672 // A server-based sync key on an account-wide basis (EAS needs this) 1673 public static final String SYNC_KEY = "syncKey"; 1674 // The default sync lookback period for this account 1675 public static final String SYNC_LOOKBACK = "syncLookback"; 1676 // The default sync frequency for this account, in minutes 1677 public static final String SYNC_INTERVAL = "syncInterval"; 1678 // A foreign key into the account manager, having host, login, password, port, and ssl flags 1679 public static final String HOST_AUTH_KEY_RECV = "hostAuthKeyRecv"; 1680 // (optional) A foreign key into the account manager, having host, login, password, port, 1681 // and ssl flags 1682 public static final String HOST_AUTH_KEY_SEND = "hostAuthKeySend"; 1683 // Flags 1684 public static final String FLAGS = "flags"; 1685 /** 1686 * Default account 1687 * 1688 * @deprecated This should never be used any more, as default accounts are handled 1689 * differently now 1690 */ 1691 @Deprecated 1692 public static final String IS_DEFAULT = "isDefault"; 1693 // Old-Style UUID for compatibility with previous versions 1694 @Deprecated 1695 public static final String COMPATIBILITY_UUID = "compatibilityUuid"; 1696 // User name (for outgoing messages) 1697 public static final String SENDER_NAME = "senderName"; 1698 /** 1699 * Ringtone 1700 * 1701 * @deprecated Only used for creating the database (legacy reasons) and migration. 1702 */ 1703 @Deprecated 1704 public static final String RINGTONE_URI = "ringtoneUri"; 1705 // Protocol version (arbitrary string, used by EAS currently) 1706 public static final String PROTOCOL_VERSION = "protocolVersion"; 1707 // The number of new messages (reported by the sync/download engines 1708 @Deprecated 1709 public static final String NEW_MESSAGE_COUNT = "newMessageCount"; 1710 // Legacy flags defining security (provisioning) requirements of this account; this 1711 // information is now found in the Policy table; POLICY_KEY (below) is the foreign key 1712 @Deprecated 1713 public static final String SECURITY_FLAGS = "securityFlags"; 1714 // Server-based sync key for the security policies currently enforced 1715 public static final String SECURITY_SYNC_KEY = "securitySyncKey"; 1716 // Signature to use with this account 1717 public static final String SIGNATURE = "signature"; 1718 // A foreign key into the Policy table 1719 public static final String POLICY_KEY = "policyKey"; 1720 // Max upload attachment size. 1721 public static final String MAX_ATTACHMENT_SIZE = "maxAttachmentSize"; 1722 // Current duration of the Exchange ping 1723 public static final String PING_DURATION = "pingDuration"; 1724 } 1725 1726 public interface QuickResponseColumns extends BaseColumns { 1727 // The QuickResponse text 1728 static final String TEXT = "quickResponse"; 1729 // A foreign key into the Account table owning the QuickResponse 1730 static final String ACCOUNT_KEY = "accountKey"; 1731 } 1732 1733 public interface MailboxColumns extends BaseColumns { 1734 // Use _ID instead 1735 @Deprecated 1736 public static final String ID = "_id"; 1737 // The display name of this mailbox [INDEX] 1738 static final String DISPLAY_NAME = "displayName"; 1739 // The server's identifier for this mailbox 1740 public static final String SERVER_ID = "serverId"; 1741 // The server's identifier for the parent of this mailbox (null = top-level) 1742 public static final String PARENT_SERVER_ID = "parentServerId"; 1743 // A foreign key for the parent of this mailbox (-1 = top-level, 0=uninitialized) 1744 public static final String PARENT_KEY = "parentKey"; 1745 // A foreign key to the Account that owns this mailbox 1746 public static final String ACCOUNT_KEY = "accountKey"; 1747 // The type (role) of this mailbox 1748 public static final String TYPE = "type"; 1749 // The hierarchy separator character 1750 public static final String DELIMITER = "delimiter"; 1751 // Server-based sync key or validity marker (e.g. "SyncKey" for EAS, "uidvalidity" for IMAP) 1752 public static final String SYNC_KEY = "syncKey"; 1753 // The sync lookback period for this mailbox (or null if using the account default) 1754 public static final String SYNC_LOOKBACK = "syncLookback"; 1755 // The sync frequency for this mailbox (or null if using the account default) 1756 public static final String SYNC_INTERVAL = "syncInterval"; 1757 // The time of last successful sync completion (millis) 1758 public static final String SYNC_TIME = "syncTime"; 1759 // Cached unread count 1760 public static final String UNREAD_COUNT = "unreadCount"; 1761 // Visibility of this folder in a list of folders [INDEX] 1762 public static final String FLAG_VISIBLE = "flagVisible"; 1763 // Other states, as a bit field, e.g. CHILDREN_VISIBLE, HAS_CHILDREN 1764 public static final String FLAGS = "flags"; 1765 // Backward compatible 1766 @Deprecated 1767 public static final String VISIBLE_LIMIT = "visibleLimit"; 1768 // Sync status (can be used as desired by sync services) 1769 public static final String SYNC_STATUS = "syncStatus"; 1770 // Number of messages locally available in the mailbox. 1771 public static final String MESSAGE_COUNT = "messageCount"; 1772 // The last time a message in this mailbox has been read (in millis) 1773 public static final String LAST_TOUCHED_TIME = "lastTouchedTime"; 1774 // The UIProvider sync status 1775 public static final String UI_SYNC_STATUS = "uiSyncStatus"; 1776 // The UIProvider last sync result 1777 public static final String UI_LAST_SYNC_RESULT = "uiLastSyncResult"; 1778 /** 1779 * The UIProvider sync status 1780 * 1781 * @deprecated This is no longer used by anything except for creating the database. 1782 */ 1783 @Deprecated 1784 public static final String LAST_NOTIFIED_MESSAGE_KEY = "lastNotifiedMessageKey"; 1785 /** 1786 * The UIProvider last sync result 1787 * 1788 * @deprecated This is no longer used by anything except for creating the database. 1789 */ 1790 @Deprecated 1791 public static final String LAST_NOTIFIED_MESSAGE_COUNT = "lastNotifiedMessageCount"; 1792 // The total number of messages in the remote mailbox 1793 public static final String TOTAL_COUNT = "totalCount"; 1794 // The full hierarchical name of this folder, in the form a/b/c 1795 public static final String HIERARCHICAL_NAME = "hierarchicalName"; 1796 // The last time that we did a full sync. Set from SystemClock.elapsedRealtime(). 1797 public static final String LAST_FULL_SYNC_TIME = "lastFullSyncTime"; 1798 } 1799 1800 public interface HostAuthColumns extends BaseColumns { 1801 // The protocol (e.g. "imap", "pop3", "eas", "smtp" 1802 static final String PROTOCOL = "protocol"; 1803 // The host address 1804 static final String ADDRESS = "address"; 1805 // The port to use for the connection 1806 static final String PORT = "port"; 1807 // General purpose flags 1808 static final String FLAGS = "flags"; 1809 // The login (user name) 1810 static final String LOGIN = "login"; 1811 // Password 1812 static final String PASSWORD = "password"; 1813 // A domain or path, if required (used in IMAP and EAS) 1814 static final String DOMAIN = "domain"; 1815 // An alias to a local client certificate for SSL 1816 static final String CLIENT_CERT_ALIAS = "certAlias"; 1817 // DEPRECATED - Will not be set or stored 1818 static final String ACCOUNT_KEY = "accountKey"; 1819 // A blob containing an X509 server certificate 1820 static final String SERVER_CERT = "serverCert"; 1821 // The credentials row this hostAuth should use. Currently only set if using OAuth. 1822 static final String CREDENTIAL_KEY = "credentialKey"; 1823 } 1824 1825 public interface PolicyColumns extends BaseColumns { 1826 public static final String PASSWORD_MODE = "passwordMode"; 1827 public static final String PASSWORD_MIN_LENGTH = "passwordMinLength"; 1828 public static final String PASSWORD_EXPIRATION_DAYS = "passwordExpirationDays"; 1829 public static final String PASSWORD_HISTORY = "passwordHistory"; 1830 public static final String PASSWORD_COMPLEX_CHARS = "passwordComplexChars"; 1831 public static final String PASSWORD_MAX_FAILS = "passwordMaxFails"; 1832 public static final String MAX_SCREEN_LOCK_TIME = "maxScreenLockTime"; 1833 public static final String REQUIRE_REMOTE_WIPE = "requireRemoteWipe"; 1834 public static final String REQUIRE_ENCRYPTION = "requireEncryption"; 1835 public static final String REQUIRE_ENCRYPTION_EXTERNAL = "requireEncryptionExternal"; 1836 // ICS additions 1837 // Note: the appearance of these columns does not imply that we support these features; only 1838 // that we store them in the Policy structure 1839 public static final String REQUIRE_MANUAL_SYNC_WHEN_ROAMING = "requireManualSyncRoaming"; 1840 public static final String DONT_ALLOW_CAMERA = "dontAllowCamera"; 1841 public static final String DONT_ALLOW_ATTACHMENTS = "dontAllowAttachments"; 1842 public static final String DONT_ALLOW_HTML = "dontAllowHtml"; 1843 public static final String MAX_ATTACHMENT_SIZE = "maxAttachmentSize"; 1844 public static final String MAX_TEXT_TRUNCATION_SIZE = "maxTextTruncationSize"; 1845 public static final String MAX_HTML_TRUNCATION_SIZE = "maxHTMLTruncationSize"; 1846 public static final String MAX_EMAIL_LOOKBACK = "maxEmailLookback"; 1847 public static final String MAX_CALENDAR_LOOKBACK = "maxCalendarLookback"; 1848 // Indicates that the server allows password recovery, not that we support it 1849 public static final String PASSWORD_RECOVERY_ENABLED = "passwordRecoveryEnabled"; 1850 // Tokenized strings indicating protocol specific policies enforced/unsupported 1851 public static final String PROTOCOL_POLICIES_ENFORCED = "protocolPoliciesEnforced"; 1852 public static final String PROTOCOL_POLICIES_UNSUPPORTED = "protocolPoliciesUnsupported"; 1853 } 1854 } 1855