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