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