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(Context context, long mailboxId) { 969 970 if (mailboxId == Mailbox.QUERY_ALL_INBOXES) { 971 return Message.ALL_INBOX_SELECTION; 972 } 973 if (mailboxId == Mailbox.QUERY_ALL_DRAFTS) { 974 return Message.ALL_DRAFT_SELECTION; 975 } 976 if (mailboxId == Mailbox.QUERY_ALL_OUTBOX) { 977 return Message.ALL_OUTBOX_SELECTION; 978 } 979 if (mailboxId == Mailbox.QUERY_ALL_UNREAD) { 980 return Message.ALL_UNREAD_SELECTION; 981 } 982 if (mailboxId == Mailbox.QUERY_ALL_FAVORITES) { 983 return Message.ALL_FAVORITE_SELECTION; 984 } 985 986 // Now it's a regular mailbox. 987 final StringBuilder selection = new StringBuilder(); 988 989 selection.append(MessageColumns.MAILBOX_KEY).append('=').append(mailboxId); 990 991 if (Mailbox.getMailboxType(context, mailboxId) != Mailbox.TYPE_OUTBOX) { 992 selection.append(" AND ").append(Message.FLAG_LOADED_SELECTION); 993 } 994 return selection.toString(); 995 } 996 } 997 998 public interface AttachmentColumns { 999 public static final String ID = "_id"; 1000 // The display name of the attachment 1001 public static final String FILENAME = "fileName"; 1002 // The mime type of the attachment 1003 public static final String MIME_TYPE = "mimeType"; 1004 // The size of the attachment in bytes 1005 public static final String SIZE = "size"; 1006 // The (internal) contentId of the attachment (inline attachments will have these) 1007 public static final String CONTENT_ID = "contentId"; 1008 // The location of the loaded attachment (probably a file) 1009 @SuppressWarnings("hiding") 1010 public static final String CONTENT_URI = "contentUri"; 1011 // A foreign key into the Message table (the message owning this attachment) 1012 public static final String MESSAGE_KEY = "messageKey"; 1013 // The location of the attachment on the server side 1014 // For IMAP, this is a part number (e.g. 2.1); for EAS, it's the internal file name 1015 public static final String LOCATION = "location"; 1016 // The transfer encoding of the attachment 1017 public static final String ENCODING = "encoding"; 1018 // Not currently used 1019 public static final String CONTENT = "content"; 1020 // Flags 1021 public static final String FLAGS = "flags"; 1022 // Content that is actually contained in the Attachment row 1023 public static final String CONTENT_BYTES = "content_bytes"; 1024 // A foreign key into the Account table (for the message owning this attachment) 1025 public static final String ACCOUNT_KEY = "accountKey"; 1026 } 1027 1028 public static final class Attachment extends EmailContent 1029 implements AttachmentColumns, Parcelable { 1030 public static final String TABLE_NAME = "Attachment"; 1031 @SuppressWarnings("hiding") 1032 public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/attachment"); 1033 // This must be used with an appended id: ContentUris.withAppendedId(MESSAGE_ID_URI, id) 1034 public static final Uri MESSAGE_ID_URI = Uri.parse( 1035 EmailContent.CONTENT_URI + "/attachment/message"); 1036 1037 public String mFileName; 1038 public String mMimeType; 1039 public long mSize; 1040 public String mContentId; 1041 public String mContentUri; 1042 public long mMessageKey; 1043 public String mLocation; 1044 public String mEncoding; 1045 public String mContent; // Not currently used 1046 public int mFlags; 1047 public byte[] mContentBytes; 1048 public long mAccountKey; 1049 1050 public static final int CONTENT_ID_COLUMN = 0; 1051 public static final int CONTENT_FILENAME_COLUMN = 1; 1052 public static final int CONTENT_MIME_TYPE_COLUMN = 2; 1053 public static final int CONTENT_SIZE_COLUMN = 3; 1054 public static final int CONTENT_CONTENT_ID_COLUMN = 4; 1055 public static final int CONTENT_CONTENT_URI_COLUMN = 5; 1056 public static final int CONTENT_MESSAGE_ID_COLUMN = 6; 1057 public static final int CONTENT_LOCATION_COLUMN = 7; 1058 public static final int CONTENT_ENCODING_COLUMN = 8; 1059 public static final int CONTENT_CONTENT_COLUMN = 9; // Not currently used 1060 public static final int CONTENT_FLAGS_COLUMN = 10; 1061 public static final int CONTENT_CONTENT_BYTES_COLUMN = 11; 1062 public static final int CONTENT_ACCOUNT_KEY_COLUMN = 12; 1063 public static final String[] CONTENT_PROJECTION = new String[] { 1064 RECORD_ID, AttachmentColumns.FILENAME, AttachmentColumns.MIME_TYPE, 1065 AttachmentColumns.SIZE, AttachmentColumns.CONTENT_ID, AttachmentColumns.CONTENT_URI, 1066 AttachmentColumns.MESSAGE_KEY, AttachmentColumns.LOCATION, AttachmentColumns.ENCODING, 1067 AttachmentColumns.CONTENT, AttachmentColumns.FLAGS, AttachmentColumns.CONTENT_BYTES, 1068 AttachmentColumns.ACCOUNT_KEY 1069 }; 1070 1071 // All attachments with an empty URI, regardless of mailbox 1072 public static final String PRECACHE_SELECTION = 1073 AttachmentColumns.CONTENT_URI + " isnull AND " + Attachment.FLAGS + "=0"; 1074 // Attachments with an empty URI that are in an inbox 1075 public static final String PRECACHE_INBOX_SELECTION = 1076 PRECACHE_SELECTION + " AND " + AttachmentColumns.MESSAGE_KEY + " IN (" 1077 + "SELECT " + MessageColumns.ID + " FROM " + Message.TABLE_NAME 1078 + " WHERE " + Message.ALL_INBOX_SELECTION 1079 + ")"; 1080 1081 // Bits used in mFlags 1082 // WARNING: AttachmentDownloadService relies on the fact that ALL of the flags below 1083 // disqualify attachments for precaching. If you add a flag that does NOT disqualify an 1084 // attachment for precaching, you MUST change the PRECACHE_SELECTION definition above 1085 1086 // Instruct Rfc822Output to 1) not use Content-Disposition and 2) use multipart/alternative 1087 // with this attachment. This is only valid if there is one and only one attachment and 1088 // that attachment has this flag set 1089 public static final int FLAG_ICS_ALTERNATIVE_PART = 1<<0; 1090 // Indicate that this attachment has been requested for downloading by the user; this is 1091 // the highest priority for attachment downloading 1092 public static final int FLAG_DOWNLOAD_USER_REQUEST = 1<<1; 1093 // Indicate that this attachment needs to be downloaded as part of an outgoing forwarded 1094 // message 1095 public static final int FLAG_DOWNLOAD_FORWARD = 1<<2; 1096 // Indicates that the attachment download failed in a non-recoverable manner 1097 public static final int FLAG_DOWNLOAD_FAILED = 1<<3; 1098 // Allow "room" for some additional download-related flags here 1099 // Indicates that the attachment will be smart-forwarded 1100 public static final int FLAG_SMART_FORWARD = 1<<8; 1101 // Indicates that the attachment cannot be forwarded due to a policy restriction 1102 public static final int FLAG_POLICY_DISALLOWS_DOWNLOAD = 1<<9; 1103 /** 1104 * no public constructor since this is a utility class 1105 */ 1106 public Attachment() { 1107 mBaseUri = CONTENT_URI; 1108 } 1109 1110 /** 1111 * Restore an Attachment from the database, given its unique id 1112 * @param context 1113 * @param id 1114 * @return the instantiated Attachment 1115 */ 1116 public static Attachment restoreAttachmentWithId(Context context, long id) { 1117 return EmailContent.restoreContentWithId(context, Attachment.class, 1118 Attachment.CONTENT_URI, Attachment.CONTENT_PROJECTION, id); 1119 } 1120 1121 /** 1122 * Restore all the Attachments of a message given its messageId 1123 */ 1124 public static Attachment[] restoreAttachmentsWithMessageId(Context context, 1125 long messageId) { 1126 Uri uri = ContentUris.withAppendedId(MESSAGE_ID_URI, messageId); 1127 Cursor c = context.getContentResolver().query(uri, CONTENT_PROJECTION, 1128 null, null, null); 1129 try { 1130 int count = c.getCount(); 1131 Attachment[] attachments = new Attachment[count]; 1132 for (int i = 0; i < count; ++i) { 1133 c.moveToNext(); 1134 Attachment attach = new Attachment(); 1135 attach.restore(c); 1136 attachments[i] = attach; 1137 } 1138 return attachments; 1139 } finally { 1140 c.close(); 1141 } 1142 } 1143 1144 /** 1145 * Creates a unique file in the external store by appending a hyphen 1146 * and a number to the given filename. 1147 * @param filename 1148 * @return a new File object, or null if one could not be created 1149 */ 1150 public static File createUniqueFile(String filename) { 1151 // TODO Handle internal storage, as required 1152 if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { 1153 File directory = Environment.getExternalStorageDirectory(); 1154 File file = new File(directory, filename); 1155 if (!file.exists()) { 1156 return file; 1157 } 1158 // Get the extension of the file, if any. 1159 int index = filename.lastIndexOf('.'); 1160 String name = filename; 1161 String extension = ""; 1162 if (index != -1) { 1163 name = filename.substring(0, index); 1164 extension = filename.substring(index); 1165 } 1166 for (int i = 2; i < Integer.MAX_VALUE; i++) { 1167 file = new File(directory, name + '-' + i + extension); 1168 if (!file.exists()) { 1169 return file; 1170 } 1171 } 1172 return null; 1173 } 1174 return null; 1175 } 1176 1177 @Override 1178 public void restore(Cursor cursor) { 1179 mBaseUri = CONTENT_URI; 1180 mId = cursor.getLong(CONTENT_ID_COLUMN); 1181 mFileName= cursor.getString(CONTENT_FILENAME_COLUMN); 1182 mMimeType = cursor.getString(CONTENT_MIME_TYPE_COLUMN); 1183 mSize = cursor.getLong(CONTENT_SIZE_COLUMN); 1184 mContentId = cursor.getString(CONTENT_CONTENT_ID_COLUMN); 1185 mContentUri = cursor.getString(CONTENT_CONTENT_URI_COLUMN); 1186 mMessageKey = cursor.getLong(CONTENT_MESSAGE_ID_COLUMN); 1187 mLocation = cursor.getString(CONTENT_LOCATION_COLUMN); 1188 mEncoding = cursor.getString(CONTENT_ENCODING_COLUMN); 1189 mContent = cursor.getString(CONTENT_CONTENT_COLUMN); 1190 mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN); 1191 mContentBytes = cursor.getBlob(CONTENT_CONTENT_BYTES_COLUMN); 1192 mAccountKey = cursor.getLong(CONTENT_ACCOUNT_KEY_COLUMN); 1193 } 1194 1195 @Override 1196 public ContentValues toContentValues() { 1197 ContentValues values = new ContentValues(); 1198 values.put(AttachmentColumns.FILENAME, mFileName); 1199 values.put(AttachmentColumns.MIME_TYPE, mMimeType); 1200 values.put(AttachmentColumns.SIZE, mSize); 1201 values.put(AttachmentColumns.CONTENT_ID, mContentId); 1202 values.put(AttachmentColumns.CONTENT_URI, mContentUri); 1203 values.put(AttachmentColumns.MESSAGE_KEY, mMessageKey); 1204 values.put(AttachmentColumns.LOCATION, mLocation); 1205 values.put(AttachmentColumns.ENCODING, mEncoding); 1206 values.put(AttachmentColumns.CONTENT, mContent); 1207 values.put(AttachmentColumns.FLAGS, mFlags); 1208 values.put(AttachmentColumns.CONTENT_BYTES, mContentBytes); 1209 values.put(AttachmentColumns.ACCOUNT_KEY, mAccountKey); 1210 return values; 1211 } 1212 1213 @Override 1214 public int describeContents() { 1215 return 0; 1216 } 1217 1218 @Override 1219 public void writeToParcel(Parcel dest, int flags) { 1220 // mBaseUri is not parceled 1221 dest.writeLong(mId); 1222 dest.writeString(mFileName); 1223 dest.writeString(mMimeType); 1224 dest.writeLong(mSize); 1225 dest.writeString(mContentId); 1226 dest.writeString(mContentUri); 1227 dest.writeLong(mMessageKey); 1228 dest.writeString(mLocation); 1229 dest.writeString(mEncoding); 1230 dest.writeString(mContent); 1231 dest.writeInt(mFlags); 1232 dest.writeLong(mAccountKey); 1233 if (mContentBytes == null) { 1234 dest.writeInt(-1); 1235 } else { 1236 dest.writeInt(mContentBytes.length); 1237 dest.writeByteArray(mContentBytes); 1238 } 1239 } 1240 1241 public Attachment(Parcel in) { 1242 mBaseUri = EmailContent.Attachment.CONTENT_URI; 1243 mId = in.readLong(); 1244 mFileName = in.readString(); 1245 mMimeType = in.readString(); 1246 mSize = in.readLong(); 1247 mContentId = in.readString(); 1248 mContentUri = in.readString(); 1249 mMessageKey = in.readLong(); 1250 mLocation = in.readString(); 1251 mEncoding = in.readString(); 1252 mContent = in.readString(); 1253 mFlags = in.readInt(); 1254 mAccountKey = in.readLong(); 1255 final int contentBytesLen = in.readInt(); 1256 if (contentBytesLen == -1) { 1257 mContentBytes = null; 1258 } else { 1259 mContentBytes = new byte[contentBytesLen]; 1260 in.readByteArray(mContentBytes); 1261 } 1262 } 1263 1264 public static final Parcelable.Creator<EmailContent.Attachment> CREATOR 1265 = new Parcelable.Creator<EmailContent.Attachment>() { 1266 @Override 1267 public EmailContent.Attachment createFromParcel(Parcel in) { 1268 return new EmailContent.Attachment(in); 1269 } 1270 1271 @Override 1272 public EmailContent.Attachment[] newArray(int size) { 1273 return new EmailContent.Attachment[size]; 1274 } 1275 }; 1276 1277 @Override 1278 public String toString() { 1279 return "[" + mFileName + ", " + mMimeType + ", " + mSize + ", " + mContentId + ", " 1280 + mContentUri + ", " + mMessageKey + ", " + mLocation + ", " + mEncoding + ", " 1281 + mFlags + ", " + mContentBytes + ", " + mAccountKey + "]"; 1282 } 1283 } 1284 1285 public interface AccountColumns { 1286 public static final String ID = "_id"; 1287 // The display name of the account (user-settable) 1288 public static final String DISPLAY_NAME = "displayName"; 1289 // The email address corresponding to this account 1290 public static final String EMAIL_ADDRESS = "emailAddress"; 1291 // A server-based sync key on an account-wide basis (EAS needs this) 1292 public static final String SYNC_KEY = "syncKey"; 1293 // The default sync lookback period for this account 1294 public static final String SYNC_LOOKBACK = "syncLookback"; 1295 // The default sync frequency for this account, in minutes 1296 public static final String SYNC_INTERVAL = "syncInterval"; 1297 // A foreign key into the account manager, having host, login, password, port, and ssl flags 1298 public static final String HOST_AUTH_KEY_RECV = "hostAuthKeyRecv"; 1299 // (optional) A foreign key into the account manager, having host, login, password, port, 1300 // and ssl flags 1301 public static final String HOST_AUTH_KEY_SEND = "hostAuthKeySend"; 1302 // Flags 1303 public static final String FLAGS = "flags"; 1304 // Default account 1305 public static final String IS_DEFAULT = "isDefault"; 1306 // Old-Style UUID for compatibility with previous versions 1307 public static final String COMPATIBILITY_UUID = "compatibilityUuid"; 1308 // User name (for outgoing messages) 1309 public static final String SENDER_NAME = "senderName"; 1310 // Ringtone 1311 public static final String RINGTONE_URI = "ringtoneUri"; 1312 // Protocol version (arbitrary string, used by EAS currently) 1313 public static final String PROTOCOL_VERSION = "protocolVersion"; 1314 // The number of new messages (reported by the sync/download engines 1315 public static final String NEW_MESSAGE_COUNT = "newMessageCount"; 1316 // Legacy flags defining security (provisioning) requirements of this account; this 1317 // information is now found in the Policy table; POLICY_KEY (below) is the foreign key 1318 @Deprecated 1319 public static final String SECURITY_FLAGS = "securityFlags"; 1320 // Server-based sync key for the security policies currently enforced 1321 public static final String SECURITY_SYNC_KEY = "securitySyncKey"; 1322 // Signature to use with this account 1323 public static final String SIGNATURE = "signature"; 1324 // A foreign key into the Policy table 1325 public static final String POLICY_KEY = "policyKey"; 1326 // The last notified message id 1327 public static final String NOTIFIED_MESSAGE_ID = "notifiedMessageId"; 1328 // The most recent notified message count 1329 public static final String NOTIFIED_MESSAGE_COUNT = "notifiedMessageCount"; 1330 } 1331 1332 public interface QuickResponseColumns { 1333 // The QuickResponse text 1334 static final String TEXT = "quickResponse"; 1335 // A foreign key into the Account table owning the QuickResponse 1336 static final String ACCOUNT_KEY = "accountKey"; 1337 } 1338 1339 public interface MailboxColumns { 1340 public static final String ID = "_id"; 1341 // The display name of this mailbox [INDEX] 1342 static final String DISPLAY_NAME = "displayName"; 1343 // The server's identifier for this mailbox 1344 public static final String SERVER_ID = "serverId"; 1345 // The server's identifier for the parent of this mailbox (null = top-level) 1346 public static final String PARENT_SERVER_ID = "parentServerId"; 1347 // A foreign key for the parent of this mailbox (-1 = top-level, 0=uninitialized) 1348 public static final String PARENT_KEY = "parentKey"; 1349 // A foreign key to the Account that owns this mailbox 1350 public static final String ACCOUNT_KEY = "accountKey"; 1351 // The type (role) of this mailbox 1352 public static final String TYPE = "type"; 1353 // The hierarchy separator character 1354 public static final String DELIMITER = "delimiter"; 1355 // Server-based sync key or validity marker (e.g. "SyncKey" for EAS, "uidvalidity" for IMAP) 1356 public static final String SYNC_KEY = "syncKey"; 1357 // The sync lookback period for this mailbox (or null if using the account default) 1358 public static final String SYNC_LOOKBACK = "syncLookback"; 1359 // The sync frequency for this mailbox (or null if using the account default) 1360 public static final String SYNC_INTERVAL = "syncInterval"; 1361 // The time of last successful sync completion (millis) 1362 public static final String SYNC_TIME = "syncTime"; 1363 // Cached unread count 1364 public static final String UNREAD_COUNT = "unreadCount"; 1365 // Visibility of this folder in a list of folders [INDEX] 1366 public static final String FLAG_VISIBLE = "flagVisible"; 1367 // Other states, as a bit field, e.g. CHILDREN_VISIBLE, HAS_CHILDREN 1368 public static final String FLAGS = "flags"; 1369 // Backward compatible 1370 public static final String VISIBLE_LIMIT = "visibleLimit"; 1371 // Sync status (can be used as desired by sync services) 1372 public static final String SYNC_STATUS = "syncStatus"; 1373 // Number of messages in the mailbox. 1374 public static final String MESSAGE_COUNT = "messageCount"; 1375 // Message ID of the last 'seen' message 1376 public static final String LAST_SEEN_MESSAGE_KEY = "lastSeenMessageKey"; 1377 // The last time a message in this mailbox has been read (in millis) 1378 public static final String LAST_TOUCHED_TIME = "lastTouchedTime"; 1379 } 1380 1381 public interface HostAuthColumns { 1382 public static final String ID = "_id"; 1383 // The protocol (e.g. "imap", "pop3", "eas", "smtp" 1384 static final String PROTOCOL = "protocol"; 1385 // The host address 1386 static final String ADDRESS = "address"; 1387 // The port to use for the connection 1388 static final String PORT = "port"; 1389 // General purpose flags 1390 static final String FLAGS = "flags"; 1391 // The login (user name) 1392 static final String LOGIN = "login"; 1393 // Password 1394 static final String PASSWORD = "password"; 1395 // A domain or path, if required (used in IMAP and EAS) 1396 static final String DOMAIN = "domain"; 1397 // An alias to a local client certificate for SSL 1398 static final String CLIENT_CERT_ALIAS = "certAlias"; 1399 // DEPRECATED - Will not be set or stored 1400 static final String ACCOUNT_KEY = "accountKey"; 1401 } 1402 1403 public interface PolicyColumns { 1404 public static final String ID = "_id"; 1405 public static final String PASSWORD_MODE = "passwordMode"; 1406 public static final String PASSWORD_MIN_LENGTH = "passwordMinLength"; 1407 public static final String PASSWORD_EXPIRATION_DAYS = "passwordExpirationDays"; 1408 public static final String PASSWORD_HISTORY = "passwordHistory"; 1409 public static final String PASSWORD_COMPLEX_CHARS = "passwordComplexChars"; 1410 public static final String PASSWORD_MAX_FAILS = "passwordMaxFails"; 1411 public static final String MAX_SCREEN_LOCK_TIME = "maxScreenLockTime"; 1412 public static final String REQUIRE_REMOTE_WIPE = "requireRemoteWipe"; 1413 public static final String REQUIRE_ENCRYPTION = "requireEncryption"; 1414 public static final String REQUIRE_ENCRYPTION_EXTERNAL = "requireEncryptionExternal"; 1415 // ICS additions 1416 // Note: the appearance of these columns does not imply that we support these features; only 1417 // that we store them in the Policy structure 1418 public static final String REQUIRE_MANUAL_SYNC_WHEN_ROAMING = "requireManualSyncRoaming"; 1419 public static final String DONT_ALLOW_CAMERA = "dontAllowCamera"; 1420 public static final String DONT_ALLOW_ATTACHMENTS = "dontAllowAttachments"; 1421 public static final String DONT_ALLOW_HTML = "dontAllowHtml"; 1422 public static final String MAX_ATTACHMENT_SIZE = "maxAttachmentSize"; 1423 public static final String MAX_TEXT_TRUNCATION_SIZE = "maxTextTruncationSize"; 1424 public static final String MAX_HTML_TRUNCATION_SIZE = "maxHTMLTruncationSize"; 1425 public static final String MAX_EMAIL_LOOKBACK = "maxEmailLookback"; 1426 public static final String MAX_CALENDAR_LOOKBACK = "maxCalendarLookback"; 1427 // Indicates that the server allows password recovery, not that we support it 1428 public static final String PASSWORD_RECOVERY_ENABLED = "passwordRecoveryEnabled"; 1429 } 1430 } 1431