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