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