EmailContent.java revision 4c4e4c3515c3e3300e03f90e02a0c520dc2dff32
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 17package com.android.emailcommon.provider; 18 19import com.android.emailcommon.utility.TextUtilities; 20import com.android.emailcommon.utility.Utility; 21 22import android.content.ContentProviderOperation; 23import android.content.ContentProviderResult; 24import android.content.ContentResolver; 25import android.content.ContentUris; 26import android.content.ContentValues; 27import android.content.Context; 28import android.content.OperationApplicationException; 29import android.database.Cursor; 30import android.net.Uri; 31import android.os.Environment; 32import android.os.Parcel; 33import android.os.Parcelable; 34import android.os.RemoteException; 35import android.text.TextUtils; 36 37import java.io.File; 38import java.net.URI; 39import java.net.URISyntaxException; 40import java.util.ArrayList; 41import java.util.List; 42import java.util.UUID; 43 44 45/** 46 * EmailContent is the superclass of the various classes of content stored by EmailProvider. 47 * 48 * It is intended to include 1) column definitions for use with the Provider, and 2) convenience 49 * methods for saving and retrieving content from the Provider. 50 * 51 * This class will be used by 1) the Email process (which includes the application and 52 * EmaiLProvider) as well as 2) the Exchange process (which runs independently). It will 53 * necessarily be cloned for use in these two cases. 54 * 55 * Conventions used in naming columns: 56 * RECORD_ID is the primary key for all Email records 57 * The SyncColumns interface is used by all classes that are synced to the server directly 58 * (Mailbox and Email) 59 * 60 * <name>_KEY always refers to a foreign key 61 * <name>_ID always refers to a unique identifier (whether on client, server, etc.) 62 * 63 */ 64public abstract class EmailContent { 65 66 public static final String AUTHORITY = "com.android.email.provider"; 67 // The notifier authority is used to send notifications regarding changes to messages (insert, 68 // delete, or update) and is intended as an optimization for use by clients of message list 69 // cursors (initially, the email AppWidget). 70 public static final String NOTIFIER_AUTHORITY = "com.android.email.notifier"; 71 72 public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY); 73 public static final String PARAMETER_LIMIT = "limit"; 74 75 public static final Uri CONTENT_NOTIFIER_URI = Uri.parse("content://" + NOTIFIER_AUTHORITY); 76 77 public static final String PROVIDER_PERMISSION = "com.android.email.permission.ACCESS_PROVIDER"; 78 79 // All classes share this 80 public static final String RECORD_ID = "_id"; 81 82 public static final String[] COUNT_COLUMNS = new String[]{"count(*)"}; 83 84 /** 85 * This projection can be used with any of the EmailContent classes, when all you need 86 * is a list of id's. Use ID_PROJECTION_COLUMN to access the row data. 87 */ 88 public static final String[] ID_PROJECTION = new String[] { 89 RECORD_ID 90 }; 91 public static final int ID_PROJECTION_COLUMN = 0; 92 93 public static final String ID_SELECTION = RECORD_ID + " =?"; 94 95 public static final String FIELD_COLUMN_NAME = "field"; 96 public static final String ADD_COLUMN_NAME = "add"; 97 public static final String SET_COLUMN_NAME = "set"; 98 99 // Newly created objects get this id 100 public static final int NOT_SAVED = -1; 101 // The base Uri that this piece of content came from 102 public Uri mBaseUri; 103 // Lazily initialized uri for this Content 104 private Uri mUri = null; 105 // The id of the Content 106 public long mId = NOT_SAVED; 107 108 // Write the Content into a ContentValues container 109 public abstract ContentValues toContentValues(); 110 // Read the Content from a ContentCursor 111 public abstract void restore (Cursor cursor); 112 113 // The Uri is lazily initialized 114 public Uri getUri() { 115 if (mUri == null) { 116 mUri = ContentUris.withAppendedId(mBaseUri, mId); 117 } 118 return mUri; 119 } 120 121 public boolean isSaved() { 122 return mId != NOT_SAVED; 123 } 124 125 126 /** 127 * Restore a subclass of EmailContent from the database 128 * @param context the caller's context 129 * @param klass the class to restore 130 * @param contentUri the content uri of the EmailContent subclass 131 * @param contentProjection the content projection for the EmailContent subclass 132 * @param id the unique id of the object 133 * @return the instantiated object 134 */ 135 public static <T extends EmailContent> T restoreContentWithId(Context context, 136 Class<T> klass, Uri contentUri, String[] contentProjection, long id) { 137 Uri u = ContentUris.withAppendedId(contentUri, id); 138 Cursor c = context.getContentResolver().query(u, contentProjection, null, null, null); 139 if (c == null) throw new ProviderUnavailableException(); 140 try { 141 if (c.moveToFirst()) { 142 return getContent(c, klass); 143 } else { 144 return null; 145 } 146 } finally { 147 c.close(); 148 } 149 } 150 151 152 // The Content sub class must have a no-arg constructor 153 static public <T extends EmailContent> T getContent(Cursor cursor, Class<T> klass) { 154 try { 155 T content = klass.newInstance(); 156 content.mId = cursor.getLong(0); 157 content.restore(cursor); 158 return content; 159 } catch (IllegalAccessException e) { 160 e.printStackTrace(); 161 } catch (InstantiationException e) { 162 e.printStackTrace(); 163 } 164 return null; 165 } 166 167 public Uri save(Context context) { 168 if (isSaved()) { 169 throw new UnsupportedOperationException(); 170 } 171 Uri res = context.getContentResolver().insert(mBaseUri, toContentValues()); 172 mId = Long.parseLong(res.getPathSegments().get(1)); 173 return res; 174 } 175 176 public int update(Context context, ContentValues contentValues) { 177 if (!isSaved()) { 178 throw new UnsupportedOperationException(); 179 } 180 return context.getContentResolver().update(getUri(), contentValues, null, null); 181 } 182 183 static public int update(Context context, Uri baseUri, long id, ContentValues contentValues) { 184 return context.getContentResolver() 185 .update(ContentUris.withAppendedId(baseUri, id), contentValues, null, null); 186 } 187 188 static public int delete(Context context, Uri baseUri, long id) { 189 return context.getContentResolver() 190 .delete(ContentUris.withAppendedId(baseUri, id), null, null); 191 } 192 193 /** 194 * Generic count method that can be used for any ContentProvider 195 * 196 * @param context the calling Context 197 * @param uri the Uri for the provider query 198 * @param selection as with a query call 199 * @param selectionArgs as with a query call 200 * @return the number of items matching the query (or zero) 201 */ 202 static public int count(Context context, Uri uri, String selection, String[] selectionArgs) { 203 return Utility.getFirstRowLong(context, 204 uri, COUNT_COLUMNS, selection, selectionArgs, null, 0, Long.valueOf(0)).intValue(); 205 } 206 207 /** 208 * Same as {@link #count(Context, Uri, String, String[])} without selection. 209 */ 210 static public int count(Context context, Uri uri) { 211 return count(context, uri, null, null); 212 } 213 214 static public Uri uriWithLimit(Uri uri, int limit) { 215 return uri.buildUpon().appendQueryParameter(EmailContent.PARAMETER_LIMIT, 216 Integer.toString(limit)).build(); 217 } 218 219 /** 220 * no public constructor since this is a utility class 221 */ 222 protected EmailContent() { 223 } 224 225 public interface SyncColumns { 226 public static final String ID = "_id"; 227 // source id (string) : the source's name of this item 228 public static final String SERVER_ID = "syncServerId"; 229 // source's timestamp (long) for this item 230 public static final String SERVER_TIMESTAMP = "syncServerTimeStamp"; 231 } 232 233 public interface BodyColumns { 234 public static final String ID = "_id"; 235 // Foreign key to the message corresponding to this body 236 public static final String MESSAGE_KEY = "messageKey"; 237 // The html content itself 238 public static final String HTML_CONTENT = "htmlContent"; 239 // The plain text content itself 240 public static final String TEXT_CONTENT = "textContent"; 241 // Replied-to or forwarded body (in html form) 242 public static final String HTML_REPLY = "htmlReply"; 243 // Replied-to or forwarded body (in text form) 244 public static final String TEXT_REPLY = "textReply"; 245 // A reference to a message's unique id used in reply/forward. 246 // Protocol code can be expected to use this column in determining whether a message can be 247 // deleted safely (i.e. isn't referenced by other messages) 248 public static final String SOURCE_MESSAGE_KEY = "sourceMessageKey"; 249 // The text to be placed between a reply/forward response and the original message 250 public static final String INTRO_TEXT = "introText"; 251 } 252 253 public static final class Body extends EmailContent implements BodyColumns { 254 public static final String TABLE_NAME = "Body"; 255 256 @SuppressWarnings("hiding") 257 public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/body"); 258 259 public static final int CONTENT_ID_COLUMN = 0; 260 public static final int CONTENT_MESSAGE_KEY_COLUMN = 1; 261 public static final int CONTENT_HTML_CONTENT_COLUMN = 2; 262 public static final int CONTENT_TEXT_CONTENT_COLUMN = 3; 263 public static final int CONTENT_HTML_REPLY_COLUMN = 4; 264 public static final int CONTENT_TEXT_REPLY_COLUMN = 5; 265 public static final int CONTENT_SOURCE_KEY_COLUMN = 6; 266 public static final int CONTENT_INTRO_TEXT_COLUMN = 7; 267 public static final String[] CONTENT_PROJECTION = new String[] { 268 RECORD_ID, BodyColumns.MESSAGE_KEY, BodyColumns.HTML_CONTENT, BodyColumns.TEXT_CONTENT, 269 BodyColumns.HTML_REPLY, BodyColumns.TEXT_REPLY, BodyColumns.SOURCE_MESSAGE_KEY, 270 BodyColumns.INTRO_TEXT 271 }; 272 273 public static final String[] COMMON_PROJECTION_TEXT = new String[] { 274 RECORD_ID, BodyColumns.TEXT_CONTENT 275 }; 276 public static final String[] COMMON_PROJECTION_HTML = new String[] { 277 RECORD_ID, BodyColumns.HTML_CONTENT 278 }; 279 public static final String[] COMMON_PROJECTION_REPLY_TEXT = new String[] { 280 RECORD_ID, BodyColumns.TEXT_REPLY 281 }; 282 public static final String[] COMMON_PROJECTION_REPLY_HTML = new String[] { 283 RECORD_ID, BodyColumns.HTML_REPLY 284 }; 285 public static final String[] COMMON_PROJECTION_INTRO = new String[] { 286 RECORD_ID, BodyColumns.INTRO_TEXT 287 }; 288 public static final String[] COMMON_PROJECTION_SOURCE = new String[] { 289 RECORD_ID, BodyColumns.SOURCE_MESSAGE_KEY 290 }; 291 public static final int COMMON_PROJECTION_COLUMN_TEXT = 1; 292 293 private static final String[] PROJECTION_SOURCE_KEY = 294 new String[] { BodyColumns.SOURCE_MESSAGE_KEY }; 295 296 public long mMessageKey; 297 public String mHtmlContent; 298 public String mTextContent; 299 public String mHtmlReply; 300 public String mTextReply; 301 public long mSourceKey; 302 public String mIntroText; 303 304 public Body() { 305 mBaseUri = CONTENT_URI; 306 } 307 308 @Override 309 public ContentValues toContentValues() { 310 ContentValues values = new ContentValues(); 311 312 // Assign values for each row. 313 values.put(BodyColumns.MESSAGE_KEY, mMessageKey); 314 values.put(BodyColumns.HTML_CONTENT, mHtmlContent); 315 values.put(BodyColumns.TEXT_CONTENT, mTextContent); 316 values.put(BodyColumns.HTML_REPLY, mHtmlReply); 317 values.put(BodyColumns.TEXT_REPLY, mTextReply); 318 values.put(BodyColumns.SOURCE_MESSAGE_KEY, mSourceKey); 319 values.put(BodyColumns.INTRO_TEXT, mIntroText); 320 return values; 321 } 322 323 /** 324 * Given a cursor, restore a Body from it 325 * @param cursor a cursor which must NOT be null 326 * @return the Body as restored from the cursor 327 */ 328 private static Body restoreBodyWithCursor(Cursor cursor) { 329 try { 330 if (cursor.moveToFirst()) { 331 return getContent(cursor, Body.class); 332 } else { 333 return null; 334 } 335 } finally { 336 cursor.close(); 337 } 338 } 339 340 public static Body restoreBodyWithId(Context context, long id) { 341 Uri u = ContentUris.withAppendedId(Body.CONTENT_URI, id); 342 Cursor c = context.getContentResolver().query(u, Body.CONTENT_PROJECTION, 343 null, null, null); 344 if (c == null) throw new ProviderUnavailableException(); 345 return restoreBodyWithCursor(c); 346 } 347 348 public static Body restoreBodyWithMessageId(Context context, long messageId) { 349 Cursor c = context.getContentResolver().query(Body.CONTENT_URI, 350 Body.CONTENT_PROJECTION, Body.MESSAGE_KEY + "=?", 351 new String[] {Long.toString(messageId)}, null); 352 if (c == null) throw new ProviderUnavailableException(); 353 return restoreBodyWithCursor(c); 354 } 355 356 /** 357 * Returns the bodyId for the given messageId, or -1 if no body is found. 358 */ 359 public static long lookupBodyIdWithMessageId(Context context, long messageId) { 360 return Utility.getFirstRowLong(context, Body.CONTENT_URI, 361 ID_PROJECTION, Body.MESSAGE_KEY + "=?", 362 new String[] {Long.toString(messageId)}, null, ID_PROJECTION_COLUMN, 363 Long.valueOf(-1)); 364 } 365 366 /** 367 * Updates the Body for a messageId with the given ContentValues. 368 * If the message has no body, a new body is inserted for the message. 369 * Warning: the argument "values" is modified by this method, setting MESSAGE_KEY. 370 */ 371 public static void updateBodyWithMessageId(Context context, long messageId, 372 ContentValues values) { 373 ContentResolver resolver = context.getContentResolver(); 374 long bodyId = lookupBodyIdWithMessageId(context, messageId); 375 values.put(BodyColumns.MESSAGE_KEY, messageId); 376 if (bodyId == -1) { 377 resolver.insert(CONTENT_URI, values); 378 } else { 379 final Uri uri = ContentUris.withAppendedId(CONTENT_URI, bodyId); 380 resolver.update(uri, values, null, null); 381 } 382 } 383 384 public static long restoreBodySourceKey(Context context, long messageId) { 385 return Utility.getFirstRowLong(context, Body.CONTENT_URI, 386 Body.PROJECTION_SOURCE_KEY, 387 Body.MESSAGE_KEY + "=?", new String[] {Long.toString(messageId)}, null, 0, 388 Long.valueOf(0)); 389 } 390 391 private static String restoreTextWithMessageId(Context context, long messageId, 392 String[] projection) { 393 Cursor c = context.getContentResolver().query(Body.CONTENT_URI, projection, 394 Body.MESSAGE_KEY + "=?", new String[] {Long.toString(messageId)}, null); 395 if (c == null) throw new ProviderUnavailableException(); 396 try { 397 if (c.moveToFirst()) { 398 return c.getString(COMMON_PROJECTION_COLUMN_TEXT); 399 } else { 400 return null; 401 } 402 } finally { 403 c.close(); 404 } 405 } 406 407 public static String restoreBodyTextWithMessageId(Context context, long messageId) { 408 return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_TEXT); 409 } 410 411 public static String restoreBodyHtmlWithMessageId(Context context, long messageId) { 412 return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_HTML); 413 } 414 415 public static String restoreReplyTextWithMessageId(Context context, long messageId) { 416 return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_REPLY_TEXT); 417 } 418 419 public static String restoreReplyHtmlWithMessageId(Context context, long messageId) { 420 return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_REPLY_HTML); 421 } 422 423 public static String restoreIntroTextWithMessageId(Context context, long messageId) { 424 return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_INTRO); 425 } 426 427 @Override 428 public void restore(Cursor cursor) { 429 mBaseUri = EmailContent.Body.CONTENT_URI; 430 mMessageKey = cursor.getLong(CONTENT_MESSAGE_KEY_COLUMN); 431 mHtmlContent = cursor.getString(CONTENT_HTML_CONTENT_COLUMN); 432 mTextContent = cursor.getString(CONTENT_TEXT_CONTENT_COLUMN); 433 mHtmlReply = cursor.getString(CONTENT_HTML_REPLY_COLUMN); 434 mTextReply = cursor.getString(CONTENT_TEXT_REPLY_COLUMN); 435 mSourceKey = cursor.getLong(CONTENT_SOURCE_KEY_COLUMN); 436 mIntroText = cursor.getString(CONTENT_INTRO_TEXT_COLUMN); 437 } 438 439 public boolean update() { 440 // TODO Auto-generated method stub 441 return false; 442 } 443 } 444 445 public interface MessageColumns { 446 public static final String ID = "_id"; 447 // Basic columns used in message list presentation 448 // The name as shown to the user in a message list 449 public static final String DISPLAY_NAME = "displayName"; 450 // The time (millis) as shown to the user in a message list [INDEX] 451 public static final String TIMESTAMP = "timeStamp"; 452 // Message subject 453 public static final String SUBJECT = "subject"; 454 // Boolean, unread = 0, read = 1 [INDEX] 455 public static final String FLAG_READ = "flagRead"; 456 // Load state, see constants below (unloaded, partial, complete, deleted) 457 public static final String FLAG_LOADED = "flagLoaded"; 458 // Boolean, unflagged = 0, flagged (favorite) = 1 459 public static final String FLAG_FAVORITE = "flagFavorite"; 460 // Boolean, no attachment = 0, attachment = 1 461 public static final String FLAG_ATTACHMENT = "flagAttachment"; 462 // Bit field for flags which we'll not be selecting on 463 public static final String FLAGS = "flags"; 464 465 // Sync related identifiers 466 // Any client-required identifier 467 public static final String CLIENT_ID = "clientId"; 468 // The message-id in the message's header 469 public static final String MESSAGE_ID = "messageId"; 470 471 // References to other Email objects in the database 472 // Foreign key to the Mailbox holding this message [INDEX] 473 public static final String MAILBOX_KEY = "mailboxKey"; 474 // Foreign key to the Account holding this message 475 public static final String ACCOUNT_KEY = "accountKey"; 476 477 // Address lists, packed with Address.pack() 478 public static final String FROM_LIST = "fromList"; 479 public static final String TO_LIST = "toList"; 480 public static final String CC_LIST = "ccList"; 481 public static final String BCC_LIST = "bccList"; 482 public static final String REPLY_TO_LIST = "replyToList"; 483 // Meeting invitation related information (for now, start time in ms) 484 public static final String MEETING_INFO = "meetingInfo"; 485 // A text "snippet" derived from the body of the message 486 public static final String SNIPPET = "snippet"; 487 } 488 489 public static final class Message extends EmailContent implements SyncColumns, MessageColumns { 490 public static final String TABLE_NAME = "Message"; 491 public static final String UPDATED_TABLE_NAME = "Message_Updates"; 492 public static final String DELETED_TABLE_NAME = "Message_Deletes"; 493 494 // To refer to a specific message, use ContentUris.withAppendedId(CONTENT_URI, id) 495 @SuppressWarnings("hiding") 496 public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/message"); 497 public static final Uri CONTENT_URI_LIMIT_1 = uriWithLimit(CONTENT_URI, 1); 498 public static final Uri SYNCED_CONTENT_URI = 499 Uri.parse(EmailContent.CONTENT_URI + "/syncedMessage"); 500 public static final Uri DELETED_CONTENT_URI = 501 Uri.parse(EmailContent.CONTENT_URI + "/deletedMessage"); 502 public static final Uri UPDATED_CONTENT_URI = 503 Uri.parse(EmailContent.CONTENT_URI + "/updatedMessage"); 504 public static final Uri NOTIFIER_URI = 505 Uri.parse(EmailContent.CONTENT_NOTIFIER_URI + "/message"); 506 507 public static final String KEY_TIMESTAMP_DESC = MessageColumns.TIMESTAMP + " desc"; 508 509 public static final int CONTENT_ID_COLUMN = 0; 510 public static final int CONTENT_DISPLAY_NAME_COLUMN = 1; 511 public static final int CONTENT_TIMESTAMP_COLUMN = 2; 512 public static final int CONTENT_SUBJECT_COLUMN = 3; 513 public static final int CONTENT_FLAG_READ_COLUMN = 4; 514 public static final int CONTENT_FLAG_LOADED_COLUMN = 5; 515 public static final int CONTENT_FLAG_FAVORITE_COLUMN = 6; 516 public static final int CONTENT_FLAG_ATTACHMENT_COLUMN = 7; 517 public static final int CONTENT_FLAGS_COLUMN = 8; 518 public static final int CONTENT_SERVER_ID_COLUMN = 9; 519 public static final int CONTENT_CLIENT_ID_COLUMN = 10; 520 public static final int CONTENT_MESSAGE_ID_COLUMN = 11; 521 public static final int CONTENT_MAILBOX_KEY_COLUMN = 12; 522 public static final int CONTENT_ACCOUNT_KEY_COLUMN = 13; 523 public static final int CONTENT_FROM_LIST_COLUMN = 14; 524 public static final int CONTENT_TO_LIST_COLUMN = 15; 525 public static final int CONTENT_CC_LIST_COLUMN = 16; 526 public static final int CONTENT_BCC_LIST_COLUMN = 17; 527 public static final int CONTENT_REPLY_TO_COLUMN = 18; 528 public static final int CONTENT_SERVER_TIMESTAMP_COLUMN = 19; 529 public static final int CONTENT_MEETING_INFO_COLUMN = 20; 530 public static final int CONTENT_SNIPPET_COLUMN = 21; 531 532 public static final String[] CONTENT_PROJECTION = new String[] { 533 RECORD_ID, 534 MessageColumns.DISPLAY_NAME, MessageColumns.TIMESTAMP, 535 MessageColumns.SUBJECT, MessageColumns.FLAG_READ, 536 MessageColumns.FLAG_LOADED, MessageColumns.FLAG_FAVORITE, 537 MessageColumns.FLAG_ATTACHMENT, MessageColumns.FLAGS, 538 SyncColumns.SERVER_ID, MessageColumns.CLIENT_ID, 539 MessageColumns.MESSAGE_ID, MessageColumns.MAILBOX_KEY, 540 MessageColumns.ACCOUNT_KEY, MessageColumns.FROM_LIST, 541 MessageColumns.TO_LIST, MessageColumns.CC_LIST, 542 MessageColumns.BCC_LIST, MessageColumns.REPLY_TO_LIST, 543 SyncColumns.SERVER_TIMESTAMP, MessageColumns.MEETING_INFO, 544 MessageColumns.SNIPPET 545 }; 546 547 public static final int LIST_ID_COLUMN = 0; 548 public static final int LIST_DISPLAY_NAME_COLUMN = 1; 549 public static final int LIST_TIMESTAMP_COLUMN = 2; 550 public static final int LIST_SUBJECT_COLUMN = 3; 551 public static final int LIST_READ_COLUMN = 4; 552 public static final int LIST_LOADED_COLUMN = 5; 553 public static final int LIST_FAVORITE_COLUMN = 6; 554 public static final int LIST_ATTACHMENT_COLUMN = 7; 555 public static final int LIST_FLAGS_COLUMN = 8; 556 public static final int LIST_MAILBOX_KEY_COLUMN = 9; 557 public static final int LIST_ACCOUNT_KEY_COLUMN = 10; 558 public static final int LIST_SERVER_ID_COLUMN = 11; 559 public static final int LIST_SNIPPET_COLUMN = 12; 560 561 // Public projection for common list columns 562 public static final String[] LIST_PROJECTION = new String[] { 563 RECORD_ID, 564 MessageColumns.DISPLAY_NAME, MessageColumns.TIMESTAMP, 565 MessageColumns.SUBJECT, MessageColumns.FLAG_READ, 566 MessageColumns.FLAG_LOADED, MessageColumns.FLAG_FAVORITE, 567 MessageColumns.FLAG_ATTACHMENT, MessageColumns.FLAGS, 568 MessageColumns.MAILBOX_KEY, MessageColumns.ACCOUNT_KEY, 569 SyncColumns.SERVER_ID, MessageColumns.SNIPPET 570 }; 571 572 public static final int ID_COLUMNS_ID_COLUMN = 0; 573 public static final int ID_COLUMNS_SYNC_SERVER_ID = 1; 574 public static final String[] ID_COLUMNS_PROJECTION = new String[] { 575 RECORD_ID, SyncColumns.SERVER_ID 576 }; 577 578 public static final int ID_MAILBOX_COLUMN_ID = 0; 579 public static final int ID_MAILBOX_COLUMN_MAILBOX_KEY = 1; 580 public static final String[] ID_MAILBOX_PROJECTION = new String[] { 581 RECORD_ID, MessageColumns.MAILBOX_KEY 582 }; 583 584 public static final String[] ID_COLUMN_PROJECTION = new String[] { RECORD_ID }; 585 586 private static final String ACCOUNT_KEY_SELECTION = 587 MessageColumns.ACCOUNT_KEY + "=?"; 588 589 /** 590 * Selection for messages that are loaded 591 * 592 * POP messages at the initial stage have very little information. (Server UID only) 593 * Use this to make sure they're not visible on any UI. 594 * This means unread counts on the mailbox list can be different from the 595 * number of messages in the message list, but it should be transient... 596 */ 597 public static final String FLAG_LOADED_SELECTION = 598 MessageColumns.FLAG_LOADED + " IN (" 599 + Message.FLAG_LOADED_PARTIAL + "," + Message.FLAG_LOADED_COMPLETE 600 + ")"; 601 602 public static final String ALL_FAVORITE_SELECTION = 603 MessageColumns.FLAG_FAVORITE + "=1 AND " 604 + MessageColumns.MAILBOX_KEY + " NOT IN (" 605 + "SELECT " + MailboxColumns.ID + " FROM " + Mailbox.TABLE_NAME + "" 606 + " WHERE " + MailboxColumns.TYPE + " = " + Mailbox.TYPE_TRASH 607 + ")" 608 + " AND " + FLAG_LOADED_SELECTION; 609 610 /** Selection to retrieve all messages in "inbox" for any account */ 611 public static final String ALL_INBOX_SELECTION = 612 MessageColumns.MAILBOX_KEY + " IN (" 613 + "SELECT " + MailboxColumns.ID + " FROM " + Mailbox.TABLE_NAME 614 + " WHERE " + MailboxColumns.TYPE + " = " + Mailbox.TYPE_INBOX 615 + ")" 616 + " AND " + FLAG_LOADED_SELECTION; 617 618 /** Selection to retrieve all messages in "drafts" for any account */ 619 public static final String ALL_DRAFT_SELECTION = 620 MessageColumns.MAILBOX_KEY + " IN (" 621 + "SELECT " + MailboxColumns.ID + " FROM " + Mailbox.TABLE_NAME 622 + " WHERE " + MailboxColumns.TYPE + " = " + Mailbox.TYPE_DRAFTS 623 + ")" 624 + " AND " + FLAG_LOADED_SELECTION; 625 626 /** Selection to retrieve all messages in "outbox" for any account */ 627 public static final String ALL_OUTBOX_SELECTION = 628 MessageColumns.MAILBOX_KEY + " IN (" 629 + "SELECT " + MailboxColumns.ID + " FROM " + Mailbox.TABLE_NAME 630 + " WHERE " + MailboxColumns.TYPE + " = " + Mailbox.TYPE_OUTBOX 631 + ")"; // NOTE No flag_loaded test for outboxes. 632 633 /** Selection to retrieve unread messages in "inbox" for any account */ 634 public static final String ALL_UNREAD_SELECTION = 635 MessageColumns.FLAG_READ + "=0 AND " + ALL_INBOX_SELECTION; 636 637 /** Selection to retrieve all messages in "inbox" for one account */ 638 public static final String PER_ACCOUNT_INBOX_SELECTION = 639 ACCOUNT_KEY_SELECTION + " AND " + ALL_INBOX_SELECTION; 640 641 private static final String ACCOUNT_FAVORITE_SELECTION = 642 ACCOUNT_KEY_SELECTION + " AND " + ALL_FAVORITE_SELECTION; 643 644 // _id field is in AbstractContent 645 public String mDisplayName; 646 public long mTimeStamp; 647 public String mSubject; 648 public boolean mFlagRead = false; 649 public int mFlagLoaded = FLAG_LOADED_UNLOADED; 650 public boolean mFlagFavorite = false; 651 public boolean mFlagAttachment = false; 652 public int mFlags = 0; 653 654 public String mServerId; 655 public long mServerTimeStamp; 656 public String mClientId; 657 public String mMessageId; 658 659 public long mMailboxKey; 660 public long mAccountKey; 661 662 public String mFrom; 663 public String mTo; 664 public String mCc; 665 public String mBcc; 666 public String mReplyTo; 667 668 // For now, just the start time of a meeting invite, in ms 669 public String mMeetingInfo; 670 671 public String mSnippet; 672 673 // The following transient members may be used while building and manipulating messages, 674 // but they are NOT persisted directly by EmailProvider 675 transient public String mText; 676 transient public String mHtml; 677 transient public String mTextReply; 678 transient public String mHtmlReply; 679 transient public long mSourceKey; 680 transient public ArrayList<Attachment> mAttachments = null; 681 transient public String mIntroText; 682 683 // Values used in mFlagRead 684 public static final int UNREAD = 0; 685 public static final int READ = 1; 686 687 // Values used in mFlagLoaded 688 public static final int FLAG_LOADED_UNLOADED = 0; 689 public static final int FLAG_LOADED_COMPLETE = 1; 690 public static final int FLAG_LOADED_PARTIAL = 2; 691 public static final int FLAG_LOADED_DELETED = 3; 692 693 // Bits used in mFlags 694 // The following three states are mutually exclusive, and indicate whether the message is an 695 // original, a reply, or a forward 696 public static final int FLAG_TYPE_ORIGINAL = 0; 697 public static final int FLAG_TYPE_REPLY = 1<<0; 698 public static final int FLAG_TYPE_FORWARD = 1<<1; 699 public static final int FLAG_TYPE_MASK = FLAG_TYPE_REPLY | FLAG_TYPE_FORWARD; 700 // The following flags indicate messages that are determined to be incoming meeting related 701 // (e.g. invites from others) 702 public static final int FLAG_INCOMING_MEETING_INVITE = 1<<2; 703 public static final int FLAG_INCOMING_MEETING_CANCEL = 1<<3; 704 public static final int FLAG_INCOMING_MEETING_MASK = 705 FLAG_INCOMING_MEETING_INVITE | FLAG_INCOMING_MEETING_CANCEL; 706 // The following flags indicate messages that are outgoing and meeting related 707 // (e.g. invites TO others) 708 public static final int FLAG_OUTGOING_MEETING_INVITE = 1<<4; 709 public static final int FLAG_OUTGOING_MEETING_CANCEL = 1<<5; 710 public static final int FLAG_OUTGOING_MEETING_ACCEPT = 1<<6; 711 public static final int FLAG_OUTGOING_MEETING_DECLINE = 1<<7; 712 public static final int FLAG_OUTGOING_MEETING_TENTATIVE = 1<<8; 713 public static final int FLAG_OUTGOING_MEETING_MASK = 714 FLAG_OUTGOING_MEETING_INVITE | FLAG_OUTGOING_MEETING_CANCEL | 715 FLAG_OUTGOING_MEETING_ACCEPT | FLAG_OUTGOING_MEETING_DECLINE | 716 FLAG_OUTGOING_MEETING_TENTATIVE; 717 public static final int FLAG_OUTGOING_MEETING_REQUEST_MASK = 718 FLAG_OUTGOING_MEETING_INVITE | FLAG_OUTGOING_MEETING_CANCEL; 719 // 8 general purpose flags (bits) that may be used at the discretion of the sync adapter 720 public static final int FLAG_SYNC_ADAPTER_SHIFT = 9; 721 public static final int FLAG_SYNC_ADAPTER_MASK = 255 << FLAG_SYNC_ADAPTER_SHIFT; 722 /** If set, the outgoing message should *not* include the quoted original message. */ 723 public static final int FLAG_NOT_INCLUDE_QUOTED_TEXT = 1 << 17; 724 public static final int FLAG_REPLIED_TO = 1 << 18; 725 public static final int FLAG_FORWARDED = 1 << 19; 726 727 public Message() { 728 mBaseUri = CONTENT_URI; 729 } 730 731 @Override 732 public ContentValues toContentValues() { 733 ContentValues values = new ContentValues(); 734 735 // Assign values for each row. 736 values.put(MessageColumns.DISPLAY_NAME, mDisplayName); 737 values.put(MessageColumns.TIMESTAMP, mTimeStamp); 738 values.put(MessageColumns.SUBJECT, mSubject); 739 values.put(MessageColumns.FLAG_READ, mFlagRead); 740 values.put(MessageColumns.FLAG_LOADED, mFlagLoaded); 741 values.put(MessageColumns.FLAG_FAVORITE, mFlagFavorite); 742 values.put(MessageColumns.FLAG_ATTACHMENT, mFlagAttachment); 743 values.put(MessageColumns.FLAGS, mFlags); 744 745 values.put(SyncColumns.SERVER_ID, mServerId); 746 values.put(SyncColumns.SERVER_TIMESTAMP, mServerTimeStamp); 747 values.put(MessageColumns.CLIENT_ID, mClientId); 748 values.put(MessageColumns.MESSAGE_ID, mMessageId); 749 750 values.put(MessageColumns.MAILBOX_KEY, mMailboxKey); 751 values.put(MessageColumns.ACCOUNT_KEY, mAccountKey); 752 753 values.put(MessageColumns.FROM_LIST, mFrom); 754 values.put(MessageColumns.TO_LIST, mTo); 755 values.put(MessageColumns.CC_LIST, mCc); 756 values.put(MessageColumns.BCC_LIST, mBcc); 757 values.put(MessageColumns.REPLY_TO_LIST, mReplyTo); 758 759 values.put(MessageColumns.MEETING_INFO, mMeetingInfo); 760 761 values.put(MessageColumns.SNIPPET, mSnippet); 762 763 return values; 764 } 765 766 public static Message restoreMessageWithId(Context context, long id) { 767 return EmailContent.restoreContentWithId(context, Message.class, 768 Message.CONTENT_URI, Message.CONTENT_PROJECTION, id); 769 } 770 771 @Override 772 public void restore(Cursor cursor) { 773 mBaseUri = CONTENT_URI; 774 mId = cursor.getLong(CONTENT_ID_COLUMN); 775 mDisplayName = cursor.getString(CONTENT_DISPLAY_NAME_COLUMN); 776 mTimeStamp = cursor.getLong(CONTENT_TIMESTAMP_COLUMN); 777 mSubject = cursor.getString(CONTENT_SUBJECT_COLUMN); 778 mFlagRead = cursor.getInt(CONTENT_FLAG_READ_COLUMN) == 1; 779 mFlagLoaded = cursor.getInt(CONTENT_FLAG_LOADED_COLUMN); 780 mFlagFavorite = cursor.getInt(CONTENT_FLAG_FAVORITE_COLUMN) == 1; 781 mFlagAttachment = cursor.getInt(CONTENT_FLAG_ATTACHMENT_COLUMN) == 1; 782 mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN); 783 mServerId = cursor.getString(CONTENT_SERVER_ID_COLUMN); 784 mServerTimeStamp = cursor.getLong(CONTENT_SERVER_TIMESTAMP_COLUMN); 785 mClientId = cursor.getString(CONTENT_CLIENT_ID_COLUMN); 786 mMessageId = cursor.getString(CONTENT_MESSAGE_ID_COLUMN); 787 mMailboxKey = cursor.getLong(CONTENT_MAILBOX_KEY_COLUMN); 788 mAccountKey = cursor.getLong(CONTENT_ACCOUNT_KEY_COLUMN); 789 mFrom = cursor.getString(CONTENT_FROM_LIST_COLUMN); 790 mTo = cursor.getString(CONTENT_TO_LIST_COLUMN); 791 mCc = cursor.getString(CONTENT_CC_LIST_COLUMN); 792 mBcc = cursor.getString(CONTENT_BCC_LIST_COLUMN); 793 mReplyTo = cursor.getString(CONTENT_REPLY_TO_COLUMN); 794 mMeetingInfo = cursor.getString(CONTENT_MEETING_INFO_COLUMN); 795 mSnippet = cursor.getString(CONTENT_SNIPPET_COLUMN); 796 } 797 798 public boolean update() { 799 // TODO Auto-generated method stub 800 return false; 801 } 802 803 /* 804 * Override this so that we can store the Body first and link it to the Message 805 * Also, attachments when we get there... 806 * (non-Javadoc) 807 * @see com.android.email.provider.EmailContent#save(android.content.Context) 808 */ 809 @Override 810 public Uri save(Context context) { 811 812 boolean doSave = !isSaved(); 813 814 // This logic is in place so I can (a) short circuit the expensive stuff when 815 // possible, and (b) override (and throw) if anyone tries to call save() or update() 816 // directly for Message, which are unsupported. 817 if (mText == null && mHtml == null && mTextReply == null && mHtmlReply == null && 818 (mAttachments == null || mAttachments.isEmpty())) { 819 if (doSave) { 820 return super.save(context); 821 } else { 822 // Call update, rather than super.update in case we ever override it 823 if (update(context, toContentValues()) == 1) { 824 return getUri(); 825 } 826 return null; 827 } 828 } 829 830 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); 831 addSaveOps(ops); 832 try { 833 ContentProviderResult[] results = 834 context.getContentResolver().applyBatch(AUTHORITY, ops); 835 // If saving, set the mId's of the various saved objects 836 if (doSave) { 837 Uri u = results[0].uri; 838 mId = Long.parseLong(u.getPathSegments().get(1)); 839 if (mAttachments != null) { 840 int resultOffset = 2; 841 for (Attachment a : mAttachments) { 842 // Save the id of the attachment record 843 u = results[resultOffset++].uri; 844 if (u != null) { 845 a.mId = Long.parseLong(u.getPathSegments().get(1)); 846 } 847 a.mMessageKey = mId; 848 } 849 } 850 return u; 851 } else { 852 return null; 853 } 854 } catch (RemoteException e) { 855 // There is nothing to be done here; fail by returning null 856 } catch (OperationApplicationException e) { 857 // There is nothing to be done here; fail by returning null 858 } 859 return null; 860 } 861 862 public void addSaveOps(ArrayList<ContentProviderOperation> ops) { 863 // First, save the message 864 ContentProviderOperation.Builder b = ContentProviderOperation.newInsert(mBaseUri); 865 // Generate the snippet here, before we create the CPO for Message 866 if (mText != null) { 867 mSnippet = TextUtilities.makeSnippetFromPlainText(mText); 868 } else if (mHtml != null) { 869 mSnippet = TextUtilities.makeSnippetFromHtmlText(mHtml); 870 } 871 ops.add(b.withValues(toContentValues()).build()); 872 873 // Create and save the body 874 ContentValues cv = new ContentValues(); 875 if (mText != null) { 876 cv.put(Body.TEXT_CONTENT, mText); 877 } 878 if (mHtml != null) { 879 cv.put(Body.HTML_CONTENT, mHtml); 880 } 881 if (mTextReply != null) { 882 cv.put(Body.TEXT_REPLY, mTextReply); 883 } 884 if (mHtmlReply != null) { 885 cv.put(Body.HTML_REPLY, mHtmlReply); 886 } 887 if (mSourceKey != 0) { 888 cv.put(Body.SOURCE_MESSAGE_KEY, mSourceKey); 889 } 890 if (mIntroText != null) { 891 cv.put(Body.INTRO_TEXT, mIntroText); 892 } 893 b = ContentProviderOperation.newInsert(Body.CONTENT_URI); 894 b.withValues(cv); 895 ContentValues backValues = new ContentValues(); 896 int messageBackValue = ops.size() - 1; 897 backValues.put(Body.MESSAGE_KEY, messageBackValue); 898 ops.add(b.withValueBackReferences(backValues).build()); 899 900 // Create the attaachments, if any 901 if (mAttachments != null) { 902 for (Attachment att: mAttachments) { 903 ops.add(ContentProviderOperation.newInsert(Attachment.CONTENT_URI) 904 .withValues(att.toContentValues()) 905 .withValueBackReference(Attachment.MESSAGE_KEY, messageBackValue) 906 .build()); 907 } 908 } 909 } 910 911 /** 912 * @return number of favorite (starred) messages throughout all accounts. 913 */ 914 public static int getFavoriteMessageCount(Context context) { 915 return count(context, Message.CONTENT_URI, ALL_FAVORITE_SELECTION, null); 916 } 917 918 /** 919 * @return number of favorite (starred) messages for an account 920 */ 921 public static int getFavoriteMessageCount(Context context, long accountId) { 922 return count(context, Message.CONTENT_URI, ACCOUNT_FAVORITE_SELECTION, 923 new String[]{Long.toString(accountId)}); 924 } 925 926 public static long getKeyColumnLong(Context context, long messageId, String column) { 927 String[] columns = 928 Utility.getRowColumns(context, Message.CONTENT_URI, messageId, column); 929 if (columns != null && columns[0] != null) { 930 return Long.parseLong(columns[0]); 931 } 932 return -1; 933 } 934 935 /** 936 * Returns the where clause for a message list selection. 937 * 938 * Accesses the detabase to determine the mailbox type. DO NOT CALL FROM UI THREAD. 939 */ 940 public static String buildMessageListSelection(Context context, long mailboxId) { 941 942 if (mailboxId == Mailbox.QUERY_ALL_INBOXES) { 943 return Message.ALL_INBOX_SELECTION; 944 } 945 if (mailboxId == Mailbox.QUERY_ALL_DRAFTS) { 946 return Message.ALL_DRAFT_SELECTION; 947 } 948 if (mailboxId == Mailbox.QUERY_ALL_OUTBOX) { 949 return Message.ALL_OUTBOX_SELECTION; 950 } 951 if (mailboxId == Mailbox.QUERY_ALL_UNREAD) { 952 return Message.ALL_UNREAD_SELECTION; 953 } 954 if (mailboxId == Mailbox.QUERY_ALL_FAVORITES) { 955 return Message.ALL_FAVORITE_SELECTION; 956 } 957 958 // Now it's a regular mailbox. 959 final StringBuilder selection = new StringBuilder(); 960 961 selection.append(MessageColumns.MAILBOX_KEY).append('=').append(mailboxId); 962 963 if (Mailbox.getMailboxType(context, mailboxId) != Mailbox.TYPE_OUTBOX) { 964 selection.append(" AND ").append(Message.FLAG_LOADED_SELECTION); 965 } 966 return selection.toString(); 967 } 968 } 969 970 public interface AccountColumns { 971 public static final String ID = "_id"; 972 // The display name of the account (user-settable) 973 public static final String DISPLAY_NAME = "displayName"; 974 // The email address corresponding to this account 975 public static final String EMAIL_ADDRESS = "emailAddress"; 976 // A server-based sync key on an account-wide basis (EAS needs this) 977 public static final String SYNC_KEY = "syncKey"; 978 // The default sync lookback period for this account 979 public static final String SYNC_LOOKBACK = "syncLookback"; 980 // The default sync frequency for this account, in minutes 981 public static final String SYNC_INTERVAL = "syncInterval"; 982 // A foreign key into the account manager, having host, login, password, port, and ssl flags 983 public static final String HOST_AUTH_KEY_RECV = "hostAuthKeyRecv"; 984 // (optional) A foreign key into the account manager, having host, login, password, port, 985 // and ssl flags 986 public static final String HOST_AUTH_KEY_SEND = "hostAuthKeySend"; 987 // Flags 988 public static final String FLAGS = "flags"; 989 // Default account 990 public static final String IS_DEFAULT = "isDefault"; 991 // Old-Style UUID for compatibility with previous versions 992 public static final String COMPATIBILITY_UUID = "compatibilityUuid"; 993 // User name (for outgoing messages) 994 public static final String SENDER_NAME = "senderName"; 995 // Ringtone 996 public static final String RINGTONE_URI = "ringtoneUri"; 997 // Protocol version (arbitrary string, used by EAS currently) 998 public static final String PROTOCOL_VERSION = "protocolVersion"; 999 // The number of new messages (reported by the sync/download engines 1000 public static final String NEW_MESSAGE_COUNT = "newMessageCount"; 1001 // Legacy flags defining security (provisioning) requirements of this account; this 1002 // information is now found in the Policy table; POLICY_KEY (below) is the foreign key 1003 @Deprecated 1004 public static final String SECURITY_FLAGS = "securityFlags"; 1005 // Server-based sync key for the security policies currently enforced 1006 public static final String SECURITY_SYNC_KEY = "securitySyncKey"; 1007 // Signature to use with this account 1008 public static final String SIGNATURE = "signature"; 1009 // A foreign key into the Policy table 1010 public static final String POLICY_KEY = "policyKey"; 1011 } 1012 1013 public static final class Account extends EmailContent implements AccountColumns, Parcelable { 1014 public static final String TABLE_NAME = "Account"; 1015 @SuppressWarnings("hiding") 1016 public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/account"); 1017 public static final Uri ADD_TO_FIELD_URI = 1018 Uri.parse(EmailContent.CONTENT_URI + "/accountIdAddToField"); 1019 public static final Uri RESET_NEW_MESSAGE_COUNT_URI = 1020 Uri.parse(EmailContent.CONTENT_URI + "/resetNewMessageCount"); 1021 public static final Uri NOTIFIER_URI = 1022 Uri.parse(EmailContent.CONTENT_NOTIFIER_URI + "/account"); 1023 1024 // Define all pseudo account IDs here to avoid conflict with one another. 1025 /** 1026 * Pseudo account ID to represent a "combined account" that includes messages and mailboxes 1027 * from all defined accounts. 1028 * 1029 * <em>IMPORTANT</em>: This must never be stored to the database. 1030 */ 1031 public static final long ACCOUNT_ID_COMBINED_VIEW = 0x1000000000000000L; 1032 /** 1033 * Pseudo account ID to represent "no account". This may be used any time the account ID 1034 * may not be known or when we want to specifically select "no" account. 1035 * 1036 * <em>IMPORTANT</em>: This must never be stored to the database. 1037 */ 1038 public static final long PSEUDO_ACCOUNT_ID_NONE = -1L; 1039 1040 // Whether or not the user has asked for notifications of new mail in this account 1041 public final static int FLAGS_NOTIFY_NEW_MAIL = 1<<0; 1042 // Whether or not the user has asked for vibration notifications with all new mail 1043 public final static int FLAGS_VIBRATE_ALWAYS = 1<<1; 1044 // Bit mask for the account's deletion policy (see DELETE_POLICY_x below) 1045 public static final int FLAGS_DELETE_POLICY_MASK = 1<<2 | 1<<3; 1046 public static final int FLAGS_DELETE_POLICY_SHIFT = 2; 1047 // Whether the account is in the process of being created; any account reconciliation code 1048 // MUST ignore accounts with this bit set; in addition, ContentObservers for this data 1049 // SHOULD consider the state of this flag during operation 1050 public static final int FLAGS_INCOMPLETE = 1<<4; 1051 // Security hold is used when the device is not in compliance with security policies 1052 // required by the server; in this state, the user MUST be alerted to the need to update 1053 // security settings. Sync adapters SHOULD NOT attempt to sync when this flag is set. 1054 public static final int FLAGS_SECURITY_HOLD = 1<<5; 1055 // Whether or not the user has asked for vibration notifications when the ringer is silent 1056 public static final int FLAGS_VIBRATE_WHEN_SILENT = 1<<6; 1057 // Whether the account supports "smart forward" (i.e. the server appends the original 1058 // message along with any attachments to the outgoing message) 1059 public static final int FLAGS_SUPPORTS_SMART_FORWARD = 1<<7; 1060 // Whether the account should try to cache attachments in the background 1061 public static final int FLAGS_BACKGROUND_ATTACHMENTS = 1<<8; 1062 // Available to sync adapter 1063 public static final int FLAGS_SYNC_ADAPTER = 1<<9; 1064 // Sync disabled is a status commanded by the server; the sync adapter SHOULD NOT try to 1065 // sync mailboxes in this account automatically. A manual sync request to sync a mailbox 1066 // with sync disabled SHOULD try to sync and report any failure result via the UI. 1067 public static final int FLAGS_SYNC_DISABLED = 1<<10; 1068 1069 // Deletion policy (see FLAGS_DELETE_POLICY_MASK, above) 1070 public static final int DELETE_POLICY_NEVER = 0; 1071 public static final int DELETE_POLICY_7DAYS = 1<<0; // not supported 1072 public static final int DELETE_POLICY_ON_DELETE = 1<<1; 1073 1074 // Sentinel values for the mSyncInterval field of both Account records 1075 public static final int CHECK_INTERVAL_NEVER = -1; 1076 public static final int CHECK_INTERVAL_PUSH = -2; 1077 1078 public String mDisplayName; 1079 public String mEmailAddress; 1080 public String mSyncKey; 1081 public int mSyncLookback; 1082 public int mSyncInterval; 1083 public long mHostAuthKeyRecv; 1084 public long mHostAuthKeySend; 1085 public int mFlags; 1086 public boolean mIsDefault; // note: callers should use getDefaultAccountId() 1087 public String mCompatibilityUuid; 1088 public String mSenderName; 1089 public String mRingtoneUri; 1090 public String mProtocolVersion; 1091 public int mNewMessageCount; 1092 public String mSecuritySyncKey; 1093 public String mSignature; 1094 public long mPolicyKey; 1095 1096 // Convenience for creating an account 1097 public transient HostAuth mHostAuthRecv; 1098 public transient HostAuth mHostAuthSend; 1099 public transient Policy mPolicy; 1100 1101 public static final int CONTENT_ID_COLUMN = 0; 1102 public static final int CONTENT_DISPLAY_NAME_COLUMN = 1; 1103 public static final int CONTENT_EMAIL_ADDRESS_COLUMN = 2; 1104 public static final int CONTENT_SYNC_KEY_COLUMN = 3; 1105 public static final int CONTENT_SYNC_LOOKBACK_COLUMN = 4; 1106 public static final int CONTENT_SYNC_INTERVAL_COLUMN = 5; 1107 public static final int CONTENT_HOST_AUTH_KEY_RECV_COLUMN = 6; 1108 public static final int CONTENT_HOST_AUTH_KEY_SEND_COLUMN = 7; 1109 public static final int CONTENT_FLAGS_COLUMN = 8; 1110 public static final int CONTENT_IS_DEFAULT_COLUMN = 9; 1111 public static final int CONTENT_COMPATIBILITY_UUID_COLUMN = 10; 1112 public static final int CONTENT_SENDER_NAME_COLUMN = 11; 1113 public static final int CONTENT_RINGTONE_URI_COLUMN = 12; 1114 public static final int CONTENT_PROTOCOL_VERSION_COLUMN = 13; 1115 public static final int CONTENT_NEW_MESSAGE_COUNT_COLUMN = 14; 1116 public static final int CONTENT_SECURITY_SYNC_KEY_COLUMN = 15; 1117 public static final int CONTENT_SIGNATURE_COLUMN = 16; 1118 public static final int CONTENT_POLICY_KEY = 17; 1119 1120 public static final String[] CONTENT_PROJECTION = new String[] { 1121 RECORD_ID, AccountColumns.DISPLAY_NAME, 1122 AccountColumns.EMAIL_ADDRESS, AccountColumns.SYNC_KEY, AccountColumns.SYNC_LOOKBACK, 1123 AccountColumns.SYNC_INTERVAL, AccountColumns.HOST_AUTH_KEY_RECV, 1124 AccountColumns.HOST_AUTH_KEY_SEND, AccountColumns.FLAGS, AccountColumns.IS_DEFAULT, 1125 AccountColumns.COMPATIBILITY_UUID, AccountColumns.SENDER_NAME, 1126 AccountColumns.RINGTONE_URI, AccountColumns.PROTOCOL_VERSION, 1127 AccountColumns.NEW_MESSAGE_COUNT, AccountColumns.SECURITY_SYNC_KEY, 1128 AccountColumns.SIGNATURE, AccountColumns.POLICY_KEY 1129 }; 1130 1131 public static final int CONTENT_MAILBOX_TYPE_COLUMN = 1; 1132 1133 /** 1134 * This projection is for listing account id's only 1135 */ 1136 public static final String[] ID_TYPE_PROJECTION = new String[] { 1137 RECORD_ID, MailboxColumns.TYPE 1138 }; 1139 1140 public static final int ACCOUNT_FLAGS_COLUMN_ID = 0; 1141 public static final int ACCOUNT_FLAGS_COLUMN_FLAGS = 1; 1142 public static final String[] ACCOUNT_FLAGS_PROJECTION = new String[] { 1143 AccountColumns.ID, AccountColumns.FLAGS}; 1144 1145 public static final String MAILBOX_SELECTION = 1146 MessageColumns.MAILBOX_KEY + " =?"; 1147 1148 public static final String UNREAD_COUNT_SELECTION = 1149 MessageColumns.MAILBOX_KEY + " =? and " + MessageColumns.FLAG_READ + "= 0"; 1150 1151 private static final String UUID_SELECTION = AccountColumns.COMPATIBILITY_UUID + " =?"; 1152 1153 public static final String SECURITY_NONZERO_SELECTION = 1154 Account.POLICY_KEY + " IS NOT NULL AND " + Account.POLICY_KEY + "!=0"; 1155 1156 private static final String FIND_INBOX_SELECTION = 1157 MailboxColumns.TYPE + " = " + Mailbox.TYPE_INBOX + 1158 " AND " + MailboxColumns.ACCOUNT_KEY + " =?"; 1159 1160 /** 1161 * This projection is for searching for the default account 1162 */ 1163 private static final String[] DEFAULT_ID_PROJECTION = new String[] { 1164 RECORD_ID, IS_DEFAULT 1165 }; 1166 1167 /** 1168 * no public constructor since this is a utility class 1169 */ 1170 public Account() { 1171 mBaseUri = CONTENT_URI; 1172 1173 // other defaults (policy) 1174 mRingtoneUri = "content://settings/system/notification_sound"; 1175 mSyncInterval = -1; 1176 mSyncLookback = -1; 1177 mFlags = FLAGS_NOTIFY_NEW_MAIL; 1178 mCompatibilityUuid = UUID.randomUUID().toString(); 1179 } 1180 1181 public static Account restoreAccountWithId(Context context, long id) { 1182 return EmailContent.restoreContentWithId(context, Account.class, 1183 Account.CONTENT_URI, Account.CONTENT_PROJECTION, id); 1184 } 1185 1186 /** 1187 * Returns {@code true} if the given account ID is a "normal" account. Normal accounts 1188 * always have an ID greater than {@code 0} and not equal to any pseudo account IDs 1189 * (such as {@link #ACCOUNT_ID_COMBINED_VIEW}) 1190 */ 1191 public static boolean isNormalAccount(long accountId) { 1192 return (accountId > 0L) && (accountId != ACCOUNT_ID_COMBINED_VIEW); 1193 } 1194 1195 /** 1196 * Refresh an account that has already been loaded. This is slightly less expensive 1197 * that generating a brand-new account object. 1198 */ 1199 public void refresh(Context context) { 1200 Cursor c = context.getContentResolver().query(getUri(), Account.CONTENT_PROJECTION, 1201 null, null, null); 1202 try { 1203 c.moveToFirst(); 1204 restore(c); 1205 } finally { 1206 if (c != null) { 1207 c.close(); 1208 } 1209 } 1210 } 1211 1212 @Override 1213 public void restore(Cursor cursor) { 1214 mId = cursor.getLong(CONTENT_ID_COLUMN); 1215 mBaseUri = CONTENT_URI; 1216 mDisplayName = cursor.getString(CONTENT_DISPLAY_NAME_COLUMN); 1217 mEmailAddress = cursor.getString(CONTENT_EMAIL_ADDRESS_COLUMN); 1218 mSyncKey = cursor.getString(CONTENT_SYNC_KEY_COLUMN); 1219 mSyncLookback = cursor.getInt(CONTENT_SYNC_LOOKBACK_COLUMN); 1220 mSyncInterval = cursor.getInt(CONTENT_SYNC_INTERVAL_COLUMN); 1221 mHostAuthKeyRecv = cursor.getLong(CONTENT_HOST_AUTH_KEY_RECV_COLUMN); 1222 mHostAuthKeySend = cursor.getLong(CONTENT_HOST_AUTH_KEY_SEND_COLUMN); 1223 mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN); 1224 mIsDefault = cursor.getInt(CONTENT_IS_DEFAULT_COLUMN) == 1; 1225 mCompatibilityUuid = cursor.getString(CONTENT_COMPATIBILITY_UUID_COLUMN); 1226 mSenderName = cursor.getString(CONTENT_SENDER_NAME_COLUMN); 1227 mRingtoneUri = cursor.getString(CONTENT_RINGTONE_URI_COLUMN); 1228 mProtocolVersion = cursor.getString(CONTENT_PROTOCOL_VERSION_COLUMN); 1229 mNewMessageCount = cursor.getInt(CONTENT_NEW_MESSAGE_COUNT_COLUMN); 1230 mSecuritySyncKey = cursor.getString(CONTENT_SECURITY_SYNC_KEY_COLUMN); 1231 mSignature = cursor.getString(CONTENT_SIGNATURE_COLUMN); 1232 mPolicyKey = cursor.getLong(CONTENT_POLICY_KEY); 1233 } 1234 1235 private long getId(Uri u) { 1236 return Long.parseLong(u.getPathSegments().get(1)); 1237 } 1238 1239 /** 1240 * @return the user-visible name for the account 1241 */ 1242 public String getDisplayName() { 1243 return mDisplayName; 1244 } 1245 1246 /** 1247 * Set the description. Be sure to call save() to commit to database. 1248 * @param description the new description 1249 */ 1250 public void setDisplayName(String description) { 1251 mDisplayName = description; 1252 } 1253 1254 /** 1255 * @return the email address for this account 1256 */ 1257 public String getEmailAddress() { 1258 return mEmailAddress; 1259 } 1260 1261 /** 1262 * Set the Email address for this account. Be sure to call save() to commit to database. 1263 * @param emailAddress the new email address for this account 1264 */ 1265 public void setEmailAddress(String emailAddress) { 1266 mEmailAddress = emailAddress; 1267 } 1268 1269 /** 1270 * @return the sender's name for this account 1271 */ 1272 public String getSenderName() { 1273 return mSenderName; 1274 } 1275 1276 /** 1277 * Set the sender's name. Be sure to call save() to commit to database. 1278 * @param name the new sender name 1279 */ 1280 public void setSenderName(String name) { 1281 mSenderName = name; 1282 } 1283 1284 public String getSignature() { 1285 return mSignature; 1286 } 1287 1288 public void setSignature(String signature) { 1289 mSignature = signature; 1290 } 1291 1292 /** 1293 * @return the minutes per check (for polling) 1294 * TODO define sentinel values for "never", "push", etc. See Account.java 1295 */ 1296 public int getSyncInterval() { 1297 return mSyncInterval; 1298 } 1299 1300 /** 1301 * Set the minutes per check (for polling). Be sure to call save() to commit to database. 1302 * TODO define sentinel values for "never", "push", etc. See Account.java 1303 * @param minutes the number of minutes between polling checks 1304 */ 1305 public void setSyncInterval(int minutes) { 1306 mSyncInterval = minutes; 1307 } 1308 1309 /** 1310 * @return One of the {@code Account.SYNC_WINDOW_*} constants that represents the sync 1311 * lookback window. 1312 * TODO define sentinel values for "all", "1 month", etc. See Account.java 1313 */ 1314 public int getSyncLookback() { 1315 return mSyncLookback; 1316 } 1317 1318 /** 1319 * Set the sync lookback window. Be sure to call save() to commit to database. 1320 * TODO define sentinel values for "all", "1 month", etc. See Account.java 1321 * @param value One of the {@code Account.SYNC_WINDOW_*} constants 1322 */ 1323 public void setSyncLookback(int value) { 1324 mSyncLookback = value; 1325 } 1326 1327 /** 1328 * @return the flags for this account 1329 * @see #FLAGS_NOTIFY_NEW_MAIL 1330 * @see #FLAGS_VIBRATE_ALWAYS 1331 * @see #FLAGS_VIBRATE_WHEN_SILENT 1332 */ 1333 public int getFlags() { 1334 return mFlags; 1335 } 1336 1337 /** 1338 * Set the flags for this account 1339 * @see #FLAGS_NOTIFY_NEW_MAIL 1340 * @see #FLAGS_VIBRATE_ALWAYS 1341 * @see #FLAGS_VIBRATE_WHEN_SILENT 1342 * @param newFlags the new value for the flags 1343 */ 1344 public void setFlags(int newFlags) { 1345 mFlags = newFlags; 1346 } 1347 1348 /** 1349 * @return the ringtone Uri for this account 1350 */ 1351 public String getRingtone() { 1352 return mRingtoneUri; 1353 } 1354 1355 /** 1356 * Set the ringtone Uri for this account 1357 * @param newUri the new URI string for the ringtone for this account 1358 */ 1359 public void setRingtone(String newUri) { 1360 mRingtoneUri = newUri; 1361 } 1362 1363 /** 1364 * Set the "delete policy" as a simple 0,1,2 value set. 1365 * @param newPolicy the new delete policy 1366 */ 1367 public void setDeletePolicy(int newPolicy) { 1368 mFlags &= ~FLAGS_DELETE_POLICY_MASK; 1369 mFlags |= (newPolicy << FLAGS_DELETE_POLICY_SHIFT) & FLAGS_DELETE_POLICY_MASK; 1370 } 1371 1372 /** 1373 * Return the "delete policy" as a simple 0,1,2 value set. 1374 * @return the current delete policy 1375 */ 1376 public int getDeletePolicy() { 1377 return (mFlags & FLAGS_DELETE_POLICY_MASK) >> FLAGS_DELETE_POLICY_SHIFT; 1378 } 1379 1380 /** 1381 * Return the Uuid associated with this account. This is primarily for compatibility 1382 * with accounts set up by previous versions, because there are externals references 1383 * to the Uuid (e.g. desktop shortcuts). 1384 */ 1385 public String getUuid() { 1386 return mCompatibilityUuid; 1387 } 1388 1389 /** 1390 * For compatibility while converting to provider model, generate a "store URI" 1391 * 1392 * @return a string in the form of a Uri, as used by the other parts of the email app 1393 */ 1394 public String getStoreUri(Context context) { 1395 // reconstitute if necessary 1396 if (mHostAuthRecv == null) { 1397 mHostAuthRecv = HostAuth.restoreHostAuthWithId(context, mHostAuthKeyRecv); 1398 } 1399 // convert if available 1400 if (mHostAuthRecv != null) { 1401 String storeUri = mHostAuthRecv.getStoreUri(); 1402 if (storeUri != null) { 1403 return storeUri; 1404 } 1405 } 1406 return ""; 1407 } 1408 1409 /** 1410 * For compatibility while converting to provider model, generate a "sender URI" 1411 * 1412 * @return a string in the form of a Uri, as used by the other parts of the email app 1413 */ 1414 public String getSenderUri(Context context) { 1415 // reconstitute if necessary 1416 if (mHostAuthSend == null) { 1417 mHostAuthSend = HostAuth.restoreHostAuthWithId(context, mHostAuthKeySend); 1418 } 1419 // convert if available 1420 if (mHostAuthSend != null) { 1421 String senderUri = mHostAuthSend.getStoreUri(); 1422 if (senderUri != null) { 1423 return senderUri; 1424 } 1425 } 1426 return ""; 1427 } 1428 1429 public HostAuth getOrCreateHostAuthSend(Context context) { 1430 if (mHostAuthSend == null) { 1431 if (mHostAuthKeySend != 0) { 1432 mHostAuthSend = HostAuth.restoreHostAuthWithId(context, mHostAuthKeySend); 1433 } else { 1434 mHostAuthSend = new EmailContent.HostAuth(); 1435 } 1436 } 1437 return mHostAuthSend; 1438 } 1439 1440 public HostAuth getOrCreateHostAuthRecv(Context context) { 1441 if (mHostAuthRecv == null) { 1442 if (mHostAuthKeyRecv != 0) { 1443 mHostAuthRecv = HostAuth.restoreHostAuthWithId(context, mHostAuthKeyRecv); 1444 } else { 1445 mHostAuthRecv = new EmailContent.HostAuth(); 1446 } 1447 } 1448 return mHostAuthRecv; 1449 } 1450 1451 /** 1452 * For compatibility while converting to provider model, generate a "local store URI" 1453 * 1454 * @return a string in the form of a Uri, as used by the other parts of the email app 1455 */ 1456 public String getLocalStoreUri(Context context) { 1457 return "local://localhost/" + context.getDatabasePath(getUuid() + ".db"); 1458 } 1459 1460 /** 1461 * @return true if the instance is of an EAS account. 1462 * 1463 * NOTE This method accesses the DB if {@link #mHostAuthRecv} hasn't been restored yet. 1464 * Use caution when you use this on the main thread. 1465 */ 1466 public boolean isEasAccount(Context context) { 1467 return "eas".equals(getProtocol(context)); 1468 } 1469 1470 /** 1471 * @return true if the account supports "move messages". 1472 */ 1473 public static boolean supportsMoveMessages(Context context, long accountId) { 1474 String protocol = getProtocol(context, accountId); 1475 return "eas".equals(protocol) || "imap".equals(protocol); 1476 } 1477 1478 /** 1479 * Set the account to be the default account. If this is set to "true", when the account 1480 * is saved, all other accounts will have the same value set to "false". 1481 * @param newDefaultState the new default state - if true, others will be cleared. 1482 */ 1483 public void setDefaultAccount(boolean newDefaultState) { 1484 mIsDefault = newDefaultState; 1485 } 1486 1487 /** 1488 * Helper method for finding the default account. 1489 */ 1490 static private long getDefaultAccountWhere(Context context, String where) { 1491 return Utility.getFirstRowLong(context, CONTENT_URI, 1492 DEFAULT_ID_PROJECTION, 1493 where, null, null, 0, Long.valueOf(-1)); 1494 } 1495 1496 /** 1497 * @return {@link Uri} to this {@link Account} in the 1498 * {@code content://com.android.email.provider/account/UUID} format, which is safe to use 1499 * for desktop shortcuts. 1500 * 1501 * <p>We don't want to store _id in shortcuts, because 1502 * {@link com.android.email.provider.AccountBackupRestore} won't preserve it. 1503 */ 1504 public Uri getShortcutSafeUri() { 1505 return getShortcutSafeUriFromUuid(mCompatibilityUuid); 1506 } 1507 1508 /** 1509 * @return {@link Uri} to an {@link Account} with a {@code uuid}. 1510 */ 1511 public static Uri getShortcutSafeUriFromUuid(String uuid) { 1512 return CONTENT_URI.buildUpon().appendEncodedPath(uuid).build(); 1513 } 1514 1515 /** 1516 * Parse {@link Uri} in the {@code content://com.android.email.provider/account/ID} format 1517 * where ID = account id (used on Eclair, Android 2.0-2.1) or UUID, and return _id of 1518 * the {@link Account} associated with it. 1519 * 1520 * @param context context to access DB 1521 * @param uri URI of interest 1522 * @return _id of the {@link Account} associated with ID, or -1 if none found. 1523 */ 1524 public static long getAccountIdFromShortcutSafeUri(Context context, Uri uri) { 1525 // Make sure the URI is in the correct format. 1526 if (!"content".equals(uri.getScheme()) 1527 || !AUTHORITY.equals(uri.getAuthority())) { 1528 return -1; 1529 } 1530 1531 final List<String> ps = uri.getPathSegments(); 1532 if (ps.size() != 2 || !"account".equals(ps.get(0))) { 1533 return -1; 1534 } 1535 1536 // Now get the ID part. 1537 final String id = ps.get(1); 1538 1539 // First, see if ID can be parsed as long. (Eclair-style) 1540 // (UUIDs have '-' in them, so they are always non-parsable.) 1541 try { 1542 return Long.parseLong(id); 1543 } catch (NumberFormatException ok) { 1544 // OK, it's not a long. Continue... 1545 } 1546 1547 // Now id is a UUId. 1548 return getAccountIdFromUuid(context, id); 1549 } 1550 1551 /** 1552 * @return ID of the account with the given UUID. 1553 */ 1554 public static long getAccountIdFromUuid(Context context, String uuid) { 1555 return Utility.getFirstRowLong(context, 1556 CONTENT_URI, ID_PROJECTION, 1557 UUID_SELECTION, new String[] {uuid}, null, 0, -1L); 1558 } 1559 1560 /** 1561 * Return the id of the default account. If one hasn't been explicitly specified, return 1562 * the first one in the database. For any account saved in the DB, this must be used 1563 * to check for the default account - the mIsDefault field is set lazily and may be 1564 * incorrect. 1565 * @param context the caller's context 1566 * @return the id of the default account, or -1 if there are no accounts 1567 */ 1568 static public long getDefaultAccountId(Context context) { 1569 long id = getDefaultAccountWhere(context, AccountColumns.IS_DEFAULT + "=1"); 1570 if (id == -1) { 1571 id = getDefaultAccountWhere(context, null); 1572 } 1573 return id; 1574 } 1575 1576 /** 1577 * Given an account id, return the account's protocol 1578 * @param context the caller's context 1579 * @param accountId the id of the account to be examined 1580 * @return the account's protocol (or null if the Account or HostAuth do not exist) 1581 */ 1582 public static String getProtocol(Context context, long accountId) { 1583 Account account = Account.restoreAccountWithId(context, accountId); 1584 if (account != null) { 1585 return account.getProtocol(context); 1586 } 1587 return null; 1588 } 1589 1590 /** 1591 * Return the account's protocol 1592 * @param context the caller's context 1593 * @return the account's protocol (or null if the HostAuth doesn't not exist) 1594 */ 1595 public String getProtocol(Context context) { 1596 HostAuth hostAuth = HostAuth.restoreHostAuthWithId(context, mHostAuthKeyRecv); 1597 if (hostAuth != null) { 1598 return hostAuth.mProtocol; 1599 } 1600 return null; 1601 } 1602 1603 /** 1604 * Return the account ID for a message with a given id 1605 * 1606 * @param context the caller's context 1607 * @param messageId the id of the message 1608 * @return the account ID, or -1 if the account doesn't exist 1609 */ 1610 public static long getAccountIdForMessageId(Context context, long messageId) { 1611 return Message.getKeyColumnLong(context, messageId, MessageColumns.ACCOUNT_KEY); 1612 } 1613 1614 /** 1615 * Return the account for a message with a given id 1616 * @param context the caller's context 1617 * @param messageId the id of the message 1618 * @return the account, or null if the account doesn't exist 1619 */ 1620 public static Account getAccountForMessageId(Context context, long messageId) { 1621 long accountId = getAccountIdForMessageId(context, messageId); 1622 if (accountId != -1) { 1623 return Account.restoreAccountWithId(context, accountId); 1624 } 1625 return null; 1626 } 1627 1628 /** 1629 * @return true if an {@code accountId} is assigned to any existing account. 1630 */ 1631 public static boolean isValidId(Context context, long accountId) { 1632 return null != Utility.getFirstRowLong(context, CONTENT_URI, ID_PROJECTION, 1633 ID_SELECTION, new String[] {Long.toString(accountId)}, null, 1634 ID_PROJECTION_COLUMN); 1635 } 1636 1637 /** 1638 * Check a single account for security hold status. 1639 */ 1640 public static boolean isSecurityHold(Context context, long accountId) { 1641 return (Utility.getFirstRowLong(context, 1642 ContentUris.withAppendedId(Account.CONTENT_URI, accountId), 1643 ACCOUNT_FLAGS_PROJECTION, null, null, null, ACCOUNT_FLAGS_COLUMN_FLAGS, 0L) 1644 & Account.FLAGS_SECURITY_HOLD) != 0; 1645 } 1646 1647 /** 1648 * @return id of the "inbox" mailbox, or -1 if not found. 1649 */ 1650 public static long getInboxId(Context context, long accountId) { 1651 return Utility.getFirstRowLong(context, Mailbox.CONTENT_URI, ID_PROJECTION, 1652 FIND_INBOX_SELECTION, new String[] {Long.toString(accountId)}, null, 1653 ID_PROJECTION_COLUMN, -1L); 1654 } 1655 1656 /** 1657 * Clear all account hold flags that are set. 1658 * 1659 * (This will trigger watchers, and in particular will cause EAS to try and resync the 1660 * account(s).) 1661 */ 1662 public static void clearSecurityHoldOnAllAccounts(Context context) { 1663 ContentResolver resolver = context.getContentResolver(); 1664 Cursor c = resolver.query(Account.CONTENT_URI, ACCOUNT_FLAGS_PROJECTION, 1665 SECURITY_NONZERO_SELECTION, null, null); 1666 try { 1667 while (c.moveToNext()) { 1668 int flags = c.getInt(ACCOUNT_FLAGS_COLUMN_FLAGS); 1669 1670 if (0 != (flags & FLAGS_SECURITY_HOLD)) { 1671 ContentValues cv = new ContentValues(); 1672 cv.put(AccountColumns.FLAGS, flags & ~FLAGS_SECURITY_HOLD); 1673 long accountId = c.getLong(ACCOUNT_FLAGS_COLUMN_ID); 1674 Uri uri = ContentUris.withAppendedId(Account.CONTENT_URI, accountId); 1675 resolver.update(uri, cv, null, null); 1676 } 1677 } 1678 } finally { 1679 c.close(); 1680 } 1681 } 1682 1683 /** 1684 * Override update to enforce a single default account, and do it atomically 1685 */ 1686 @Override 1687 public int update(Context context, ContentValues cv) { 1688 if (mPolicy != null && mPolicyKey <= 0) { 1689 // If a policy is set and there's no policy, link it to the account 1690 mPolicy.setAccountPolicy(context, this, null); 1691 } 1692 if (cv.containsKey(AccountColumns.IS_DEFAULT) && 1693 cv.getAsBoolean(AccountColumns.IS_DEFAULT)) { 1694 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); 1695 ContentValues cv1 = new ContentValues(); 1696 cv1.put(AccountColumns.IS_DEFAULT, false); 1697 // Clear the default flag in all accounts 1698 ops.add(ContentProviderOperation.newUpdate(CONTENT_URI).withValues(cv1).build()); 1699 // Update this account 1700 ops.add(ContentProviderOperation 1701 .newUpdate(ContentUris.withAppendedId(CONTENT_URI, mId)) 1702 .withValues(cv).build()); 1703 try { 1704 context.getContentResolver().applyBatch(AUTHORITY, ops); 1705 return 1; 1706 } catch (RemoteException e) { 1707 // There is nothing to be done here; fail by returning 0 1708 } catch (OperationApplicationException e) { 1709 // There is nothing to be done here; fail by returning 0 1710 } 1711 return 0; 1712 } 1713 return super.update(context, cv); 1714 } 1715 1716 /* 1717 * Override this so that we can store the HostAuth's first and link them to the Account 1718 * (non-Javadoc) 1719 * @see com.android.email.provider.EmailContent#save(android.content.Context) 1720 */ 1721 @Override 1722 public Uri save(Context context) { 1723 if (isSaved()) { 1724 throw new UnsupportedOperationException(); 1725 } 1726 // This logic is in place so I can (a) short circuit the expensive stuff when 1727 // possible, and (b) override (and throw) if anyone tries to call save() or update() 1728 // directly for Account, which are unsupported. 1729 if (mHostAuthRecv == null && mHostAuthSend == null && mIsDefault == false && 1730 mPolicy != null) { 1731 return super.save(context); 1732 } 1733 1734 int index = 0; 1735 int recvIndex = -1; 1736 int sendIndex = -1; 1737 int policyIndex = -1; 1738 1739 // Create operations for saving the send and recv hostAuths 1740 // Also, remember which operation in the array they represent 1741 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); 1742 if (mHostAuthRecv != null) { 1743 recvIndex = index++; 1744 ops.add(ContentProviderOperation.newInsert(mHostAuthRecv.mBaseUri) 1745 .withValues(mHostAuthRecv.toContentValues()) 1746 .build()); 1747 } 1748 if (mHostAuthSend != null) { 1749 sendIndex = index++; 1750 ops.add(ContentProviderOperation.newInsert(mHostAuthSend.mBaseUri) 1751 .withValues(mHostAuthSend.toContentValues()) 1752 .build()); 1753 } 1754 if (mPolicy != null) { 1755 policyIndex = index++; 1756 ops.add(ContentProviderOperation.newInsert(mPolicy.mBaseUri) 1757 .withValues(mPolicy.toContentValues()) 1758 .build()); 1759 } 1760 1761 // Create operations for making this the only default account 1762 // Note, these are always updates because they change existing accounts 1763 if (mIsDefault) { 1764 index++; 1765 ContentValues cv1 = new ContentValues(); 1766 cv1.put(AccountColumns.IS_DEFAULT, 0); 1767 ops.add(ContentProviderOperation.newUpdate(CONTENT_URI).withValues(cv1).build()); 1768 } 1769 1770 // Now do the Account 1771 ContentValues cv = null; 1772 if (recvIndex >= 0 || sendIndex >= 0) { 1773 cv = new ContentValues(); 1774 if (recvIndex >= 0) { 1775 cv.put(Account.HOST_AUTH_KEY_RECV, recvIndex); 1776 } 1777 if (sendIndex >= 0) { 1778 cv.put(Account.HOST_AUTH_KEY_SEND, sendIndex); 1779 } 1780 if (policyIndex >= 0) { 1781 cv.put(Account.POLICY_KEY, policyIndex); 1782 } 1783 } 1784 1785 ContentProviderOperation.Builder b = ContentProviderOperation.newInsert(mBaseUri); 1786 b.withValues(toContentValues()); 1787 if (cv != null) { 1788 b.withValueBackReferences(cv); 1789 } 1790 ops.add(b.build()); 1791 1792 try { 1793 ContentProviderResult[] results = 1794 context.getContentResolver().applyBatch(AUTHORITY, ops); 1795 // If saving, set the mId's of the various saved objects 1796 if (recvIndex >= 0) { 1797 long newId = getId(results[recvIndex].uri); 1798 mHostAuthKeyRecv = newId; 1799 mHostAuthRecv.mId = newId; 1800 } 1801 if (sendIndex >= 0) { 1802 long newId = getId(results[sendIndex].uri); 1803 mHostAuthKeySend = newId; 1804 mHostAuthSend.mId = newId; 1805 } 1806 if (policyIndex >= 0) { 1807 long newId = getId(results[policyIndex].uri); 1808 mPolicyKey = newId; 1809 mPolicy.mId = newId; 1810 } 1811 Uri u = results[index].uri; 1812 mId = getId(u); 1813 return u; 1814 } catch (RemoteException e) { 1815 // There is nothing to be done here; fail by returning null 1816 } catch (OperationApplicationException e) { 1817 // There is nothing to be done here; fail by returning null 1818 } 1819 return null; 1820 } 1821 1822 @Override 1823 public ContentValues toContentValues() { 1824 ContentValues values = new ContentValues(); 1825 values.put(AccountColumns.DISPLAY_NAME, mDisplayName); 1826 values.put(AccountColumns.EMAIL_ADDRESS, mEmailAddress); 1827 values.put(AccountColumns.SYNC_KEY, mSyncKey); 1828 values.put(AccountColumns.SYNC_LOOKBACK, mSyncLookback); 1829 values.put(AccountColumns.SYNC_INTERVAL, mSyncInterval); 1830 values.put(AccountColumns.HOST_AUTH_KEY_RECV, mHostAuthKeyRecv); 1831 values.put(AccountColumns.HOST_AUTH_KEY_SEND, mHostAuthKeySend); 1832 values.put(AccountColumns.FLAGS, mFlags); 1833 values.put(AccountColumns.IS_DEFAULT, mIsDefault); 1834 values.put(AccountColumns.COMPATIBILITY_UUID, mCompatibilityUuid); 1835 values.put(AccountColumns.SENDER_NAME, mSenderName); 1836 values.put(AccountColumns.RINGTONE_URI, mRingtoneUri); 1837 values.put(AccountColumns.PROTOCOL_VERSION, mProtocolVersion); 1838 values.put(AccountColumns.NEW_MESSAGE_COUNT, mNewMessageCount); 1839 values.put(AccountColumns.SECURITY_SYNC_KEY, mSecuritySyncKey); 1840 values.put(AccountColumns.SIGNATURE, mSignature); 1841 values.put(AccountColumns.POLICY_KEY, mPolicyKey); 1842 return values; 1843 } 1844 1845 /** 1846 * Supports Parcelable 1847 */ 1848 @Override 1849 public int describeContents() { 1850 return 0; 1851 } 1852 1853 /** 1854 * Supports Parcelable 1855 */ 1856 public static final Parcelable.Creator<EmailContent.Account> CREATOR 1857 = new Parcelable.Creator<EmailContent.Account>() { 1858 @Override 1859 public EmailContent.Account createFromParcel(Parcel in) { 1860 return new EmailContent.Account(in); 1861 } 1862 1863 @Override 1864 public EmailContent.Account[] newArray(int size) { 1865 return new EmailContent.Account[size]; 1866 } 1867 }; 1868 1869 /** 1870 * Supports Parcelable 1871 */ 1872 @Override 1873 public void writeToParcel(Parcel dest, int flags) { 1874 // mBaseUri is not parceled 1875 dest.writeLong(mId); 1876 dest.writeString(mDisplayName); 1877 dest.writeString(mEmailAddress); 1878 dest.writeString(mSyncKey); 1879 dest.writeInt(mSyncLookback); 1880 dest.writeInt(mSyncInterval); 1881 dest.writeLong(mHostAuthKeyRecv); 1882 dest.writeLong(mHostAuthKeySend); 1883 dest.writeInt(mFlags); 1884 dest.writeByte(mIsDefault ? (byte)1 : (byte)0); 1885 dest.writeString(mCompatibilityUuid); 1886 dest.writeString(mSenderName); 1887 dest.writeString(mRingtoneUri); 1888 dest.writeString(mProtocolVersion); 1889 dest.writeInt(mNewMessageCount); 1890 dest.writeString(mSecuritySyncKey); 1891 dest.writeString(mSignature); 1892 dest.writeLong(mPolicyKey); 1893 1894 if (mHostAuthRecv != null) { 1895 dest.writeByte((byte)1); 1896 mHostAuthRecv.writeToParcel(dest, flags); 1897 } else { 1898 dest.writeByte((byte)0); 1899 } 1900 1901 if (mHostAuthSend != null) { 1902 dest.writeByte((byte)1); 1903 mHostAuthSend.writeToParcel(dest, flags); 1904 } else { 1905 dest.writeByte((byte)0); 1906 } 1907 } 1908 1909 /** 1910 * Supports Parcelable 1911 */ 1912 public Account(Parcel in) { 1913 mBaseUri = EmailContent.Account.CONTENT_URI; 1914 mId = in.readLong(); 1915 mDisplayName = in.readString(); 1916 mEmailAddress = in.readString(); 1917 mSyncKey = in.readString(); 1918 mSyncLookback = in.readInt(); 1919 mSyncInterval = in.readInt(); 1920 mHostAuthKeyRecv = in.readLong(); 1921 mHostAuthKeySend = in.readLong(); 1922 mFlags = in.readInt(); 1923 mIsDefault = in.readByte() == 1; 1924 mCompatibilityUuid = in.readString(); 1925 mSenderName = in.readString(); 1926 mRingtoneUri = in.readString(); 1927 mProtocolVersion = in.readString(); 1928 mNewMessageCount = in.readInt(); 1929 mSecuritySyncKey = in.readString(); 1930 mSignature = in.readString(); 1931 mPolicyKey = in.readLong(); 1932 1933 mHostAuthRecv = null; 1934 if (in.readByte() == 1) { 1935 mHostAuthRecv = new EmailContent.HostAuth(in); 1936 } 1937 1938 mHostAuthSend = null; 1939 if (in.readByte() == 1) { 1940 mHostAuthSend = new EmailContent.HostAuth(in); 1941 } 1942 } 1943 1944 /** 1945 * For debugger support only - DO NOT use for code. 1946 */ 1947 @Override 1948 public String toString() { 1949 StringBuilder sb = new StringBuilder('['); 1950 if (mHostAuthRecv != null && mHostAuthRecv.mProtocol != null) { 1951 sb.append(mHostAuthRecv.mProtocol); 1952 sb.append(':'); 1953 } 1954 if (mDisplayName != null) sb.append(mDisplayName); 1955 sb.append(':'); 1956 if (mEmailAddress != null) sb.append(mEmailAddress); 1957 sb.append(':'); 1958 if (mSenderName != null) sb.append(mSenderName); 1959 sb.append(']'); 1960 return sb.toString(); 1961 } 1962 1963 } 1964 1965 public interface AttachmentColumns { 1966 public static final String ID = "_id"; 1967 // The display name of the attachment 1968 public static final String FILENAME = "fileName"; 1969 // The mime type of the attachment 1970 public static final String MIME_TYPE = "mimeType"; 1971 // The size of the attachment in bytes 1972 public static final String SIZE = "size"; 1973 // The (internal) contentId of the attachment (inline attachments will have these) 1974 public static final String CONTENT_ID = "contentId"; 1975 // The location of the loaded attachment (probably a file) 1976 @SuppressWarnings("hiding") 1977 public static final String CONTENT_URI = "contentUri"; 1978 // A foreign key into the Message table (the message owning this attachment) 1979 public static final String MESSAGE_KEY = "messageKey"; 1980 // The location of the attachment on the server side 1981 // For IMAP, this is a part number (e.g. 2.1); for EAS, it's the internal file name 1982 public static final String LOCATION = "location"; 1983 // The transfer encoding of the attachment 1984 public static final String ENCODING = "encoding"; 1985 // Not currently used 1986 public static final String CONTENT = "content"; 1987 // Flags 1988 public static final String FLAGS = "flags"; 1989 // Content that is actually contained in the Attachment row 1990 public static final String CONTENT_BYTES = "content_bytes"; 1991 // A foreign key into the Account table (for the message owning this attachment) 1992 public static final String ACCOUNT_KEY = "accountKey"; 1993 } 1994 1995 public static final class Attachment extends EmailContent 1996 implements AttachmentColumns, Parcelable { 1997 public static final String TABLE_NAME = "Attachment"; 1998 @SuppressWarnings("hiding") 1999 public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/attachment"); 2000 // This must be used with an appended id: ContentUris.withAppendedId(MESSAGE_ID_URI, id) 2001 public static final Uri MESSAGE_ID_URI = Uri.parse( 2002 EmailContent.CONTENT_URI + "/attachment/message"); 2003 2004 public String mFileName; 2005 public String mMimeType; 2006 public long mSize; 2007 public String mContentId; 2008 public String mContentUri; 2009 public long mMessageKey; 2010 public String mLocation; 2011 public String mEncoding; 2012 public String mContent; // Not currently used 2013 public int mFlags; 2014 public byte[] mContentBytes; 2015 public long mAccountKey; 2016 2017 public static final int CONTENT_ID_COLUMN = 0; 2018 public static final int CONTENT_FILENAME_COLUMN = 1; 2019 public static final int CONTENT_MIME_TYPE_COLUMN = 2; 2020 public static final int CONTENT_SIZE_COLUMN = 3; 2021 public static final int CONTENT_CONTENT_ID_COLUMN = 4; 2022 public static final int CONTENT_CONTENT_URI_COLUMN = 5; 2023 public static final int CONTENT_MESSAGE_ID_COLUMN = 6; 2024 public static final int CONTENT_LOCATION_COLUMN = 7; 2025 public static final int CONTENT_ENCODING_COLUMN = 8; 2026 public static final int CONTENT_CONTENT_COLUMN = 9; // Not currently used 2027 public static final int CONTENT_FLAGS_COLUMN = 10; 2028 public static final int CONTENT_CONTENT_BYTES_COLUMN = 11; 2029 public static final int CONTENT_ACCOUNT_KEY_COLUMN = 12; 2030 public static final String[] CONTENT_PROJECTION = new String[] { 2031 RECORD_ID, AttachmentColumns.FILENAME, AttachmentColumns.MIME_TYPE, 2032 AttachmentColumns.SIZE, AttachmentColumns.CONTENT_ID, AttachmentColumns.CONTENT_URI, 2033 AttachmentColumns.MESSAGE_KEY, AttachmentColumns.LOCATION, AttachmentColumns.ENCODING, 2034 AttachmentColumns.CONTENT, AttachmentColumns.FLAGS, AttachmentColumns.CONTENT_BYTES, 2035 AttachmentColumns.ACCOUNT_KEY 2036 }; 2037 2038 // All attachments with an empty URI, regardless of mailbox 2039 public static final String PRECACHE_SELECTION = 2040 AttachmentColumns.CONTENT_URI + " isnull AND " + Attachment.FLAGS + "=0"; 2041 // Attachments with an empty URI that are in an inbox 2042 public static final String PRECACHE_INBOX_SELECTION = 2043 PRECACHE_SELECTION + " AND " + AttachmentColumns.MESSAGE_KEY + " IN (" 2044 + "SELECT " + MessageColumns.ID + " FROM " + Message.TABLE_NAME 2045 + " WHERE " + Message.ALL_INBOX_SELECTION 2046 + ")"; 2047 2048 // Bits used in mFlags 2049 // WARNING: AttachmentDownloadService relies on the fact that ALL of the flags below 2050 // disqualify attachments for precaching. If you add a flag that does NOT disqualify an 2051 // attachment for precaching, you MUST change the PRECACHE_SELECTION definition above 2052 2053 // Instruct Rfc822Output to 1) not use Content-Disposition and 2) use multipart/alternative 2054 // with this attachment. This is only valid if there is one and only one attachment and 2055 // that attachment has this flag set 2056 public static final int FLAG_ICS_ALTERNATIVE_PART = 1<<0; 2057 // Indicate that this attachment has been requested for downloading by the user; this is 2058 // the highest priority for attachment downloading 2059 public static final int FLAG_DOWNLOAD_USER_REQUEST = 1<<1; 2060 // Indicate that this attachment needs to be downloaded as part of an outgoing forwarded 2061 // message 2062 public static final int FLAG_DOWNLOAD_FORWARD = 1<<2; 2063 // Indicates that the attachment download failed in a non-recoverable manner 2064 public static final int FLAG_DOWNLOAD_FAILED = 1<<3; 2065 // Allow "room" for some additional download-related flags here 2066 // Indicates that the attachment will be smart-forwarded 2067 public static final int FLAG_SMART_FORWARD = 1<<8; 2068 // Indicates that the attachment cannot be forwarded due to a policy restriction 2069 public static final int FLAG_POLICY_DISALLOWS_DOWNLOAD = 1<<9; 2070 /** 2071 * no public constructor since this is a utility class 2072 */ 2073 public Attachment() { 2074 mBaseUri = CONTENT_URI; 2075 } 2076 2077 /** 2078 * Restore an Attachment from the database, given its unique id 2079 * @param context 2080 * @param id 2081 * @return the instantiated Attachment 2082 */ 2083 public static Attachment restoreAttachmentWithId(Context context, long id) { 2084 return EmailContent.restoreContentWithId(context, Attachment.class, 2085 Attachment.CONTENT_URI, Attachment.CONTENT_PROJECTION, id); 2086 } 2087 2088 /** 2089 * Restore all the Attachments of a message given its messageId 2090 */ 2091 public static Attachment[] restoreAttachmentsWithMessageId(Context context, 2092 long messageId) { 2093 Uri uri = ContentUris.withAppendedId(MESSAGE_ID_URI, messageId); 2094 Cursor c = context.getContentResolver().query(uri, CONTENT_PROJECTION, 2095 null, null, null); 2096 try { 2097 int count = c.getCount(); 2098 Attachment[] attachments = new Attachment[count]; 2099 for (int i = 0; i < count; ++i) { 2100 c.moveToNext(); 2101 Attachment attach = new Attachment(); 2102 attach.restore(c); 2103 attachments[i] = attach; 2104 } 2105 return attachments; 2106 } finally { 2107 c.close(); 2108 } 2109 } 2110 2111 /** 2112 * Creates a unique file in the external store by appending a hyphen 2113 * and a number to the given filename. 2114 * @param filename 2115 * @return a new File object, or null if one could not be created 2116 */ 2117 public static File createUniqueFile(String filename) { 2118 // TODO Handle internal storage, as required 2119 if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { 2120 File directory = Environment.getExternalStorageDirectory(); 2121 File file = new File(directory, filename); 2122 if (!file.exists()) { 2123 return file; 2124 } 2125 // Get the extension of the file, if any. 2126 int index = filename.lastIndexOf('.'); 2127 String name = filename; 2128 String extension = ""; 2129 if (index != -1) { 2130 name = filename.substring(0, index); 2131 extension = filename.substring(index); 2132 } 2133 for (int i = 2; i < Integer.MAX_VALUE; i++) { 2134 file = new File(directory, name + '-' + i + extension); 2135 if (!file.exists()) { 2136 return file; 2137 } 2138 } 2139 return null; 2140 } 2141 return null; 2142 } 2143 2144 @Override 2145 public void restore(Cursor cursor) { 2146 mBaseUri = CONTENT_URI; 2147 mId = cursor.getLong(CONTENT_ID_COLUMN); 2148 mFileName= cursor.getString(CONTENT_FILENAME_COLUMN); 2149 mMimeType = cursor.getString(CONTENT_MIME_TYPE_COLUMN); 2150 mSize = cursor.getLong(CONTENT_SIZE_COLUMN); 2151 mContentId = cursor.getString(CONTENT_CONTENT_ID_COLUMN); 2152 mContentUri = cursor.getString(CONTENT_CONTENT_URI_COLUMN); 2153 mMessageKey = cursor.getLong(CONTENT_MESSAGE_ID_COLUMN); 2154 mLocation = cursor.getString(CONTENT_LOCATION_COLUMN); 2155 mEncoding = cursor.getString(CONTENT_ENCODING_COLUMN); 2156 mContent = cursor.getString(CONTENT_CONTENT_COLUMN); 2157 mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN); 2158 mContentBytes = cursor.getBlob(CONTENT_CONTENT_BYTES_COLUMN); 2159 mAccountKey = cursor.getLong(CONTENT_ACCOUNT_KEY_COLUMN); 2160 } 2161 2162 @Override 2163 public ContentValues toContentValues() { 2164 ContentValues values = new ContentValues(); 2165 values.put(AttachmentColumns.FILENAME, mFileName); 2166 values.put(AttachmentColumns.MIME_TYPE, mMimeType); 2167 values.put(AttachmentColumns.SIZE, mSize); 2168 values.put(AttachmentColumns.CONTENT_ID, mContentId); 2169 values.put(AttachmentColumns.CONTENT_URI, mContentUri); 2170 values.put(AttachmentColumns.MESSAGE_KEY, mMessageKey); 2171 values.put(AttachmentColumns.LOCATION, mLocation); 2172 values.put(AttachmentColumns.ENCODING, mEncoding); 2173 values.put(AttachmentColumns.CONTENT, mContent); 2174 values.put(AttachmentColumns.FLAGS, mFlags); 2175 values.put(AttachmentColumns.CONTENT_BYTES, mContentBytes); 2176 values.put(AttachmentColumns.ACCOUNT_KEY, mAccountKey); 2177 return values; 2178 } 2179 2180 @Override 2181 public int describeContents() { 2182 return 0; 2183 } 2184 2185 @Override 2186 public void writeToParcel(Parcel dest, int flags) { 2187 // mBaseUri is not parceled 2188 dest.writeLong(mId); 2189 dest.writeString(mFileName); 2190 dest.writeString(mMimeType); 2191 dest.writeLong(mSize); 2192 dest.writeString(mContentId); 2193 dest.writeString(mContentUri); 2194 dest.writeLong(mMessageKey); 2195 dest.writeString(mLocation); 2196 dest.writeString(mEncoding); 2197 dest.writeString(mContent); 2198 dest.writeInt(mFlags); 2199 dest.writeLong(mAccountKey); 2200 if (mContentBytes == null) { 2201 dest.writeInt(-1); 2202 } else { 2203 dest.writeInt(mContentBytes.length); 2204 dest.writeByteArray(mContentBytes); 2205 } 2206 } 2207 2208 public Attachment(Parcel in) { 2209 mBaseUri = EmailContent.Attachment.CONTENT_URI; 2210 mId = in.readLong(); 2211 mFileName = in.readString(); 2212 mMimeType = in.readString(); 2213 mSize = in.readLong(); 2214 mContentId = in.readString(); 2215 mContentUri = in.readString(); 2216 mMessageKey = in.readLong(); 2217 mLocation = in.readString(); 2218 mEncoding = in.readString(); 2219 mContent = in.readString(); 2220 mFlags = in.readInt(); 2221 mAccountKey = in.readLong(); 2222 final int contentBytesLen = in.readInt(); 2223 if (contentBytesLen == -1) { 2224 mContentBytes = null; 2225 } else { 2226 mContentBytes = new byte[contentBytesLen]; 2227 in.readByteArray(mContentBytes); 2228 } 2229 } 2230 2231 public static final Parcelable.Creator<EmailContent.Attachment> CREATOR 2232 = new Parcelable.Creator<EmailContent.Attachment>() { 2233 @Override 2234 public EmailContent.Attachment createFromParcel(Parcel in) { 2235 return new EmailContent.Attachment(in); 2236 } 2237 2238 @Override 2239 public EmailContent.Attachment[] newArray(int size) { 2240 return new EmailContent.Attachment[size]; 2241 } 2242 }; 2243 2244 @Override 2245 public String toString() { 2246 return "[" + mFileName + ", " + mMimeType + ", " + mSize + ", " + mContentId + ", " 2247 + mContentUri + ", " + mMessageKey + ", " + mLocation + ", " + mEncoding + ", " 2248 + mFlags + ", " + mContentBytes + ", " + mAccountKey + "]"; 2249 } 2250 } 2251 2252 public interface MailboxColumns { 2253 public static final String ID = "_id"; 2254 // The display name of this mailbox [INDEX] 2255 static final String DISPLAY_NAME = "displayName"; 2256 // The server's identifier for this mailbox 2257 public static final String SERVER_ID = "serverId"; 2258 // The server's identifier for the parent of this mailbox (null = top-level) 2259 public static final String PARENT_SERVER_ID = "parentServerId"; 2260 // A foreign key for the parent of this mailbox (-1 = top-level, 0=uninitialized) 2261 public static final String PARENT_KEY = "parentKey"; 2262 // A foreign key to the Account that owns this mailbox 2263 public static final String ACCOUNT_KEY = "accountKey"; 2264 // The type (role) of this mailbox 2265 public static final String TYPE = "type"; 2266 // The hierarchy separator character 2267 public static final String DELIMITER = "delimiter"; 2268 // Server-based sync key or validity marker (e.g. "SyncKey" for EAS, "uidvalidity" for IMAP) 2269 public static final String SYNC_KEY = "syncKey"; 2270 // The sync lookback period for this mailbox (or null if using the account default) 2271 public static final String SYNC_LOOKBACK = "syncLookback"; 2272 // The sync frequency for this mailbox (or null if using the account default) 2273 public static final String SYNC_INTERVAL = "syncInterval"; 2274 // The time of last successful sync completion (millis) 2275 public static final String SYNC_TIME = "syncTime"; 2276 // Cached unread count 2277 public static final String UNREAD_COUNT = "unreadCount"; 2278 // Visibility of this folder in a list of folders [INDEX] 2279 public static final String FLAG_VISIBLE = "flagVisible"; 2280 // Other states, as a bit field, e.g. CHILDREN_VISIBLE, HAS_CHILDREN 2281 public static final String FLAGS = "flags"; 2282 // Backward compatible 2283 public static final String VISIBLE_LIMIT = "visibleLimit"; 2284 // Sync status (can be used as desired by sync services) 2285 public static final String SYNC_STATUS = "syncStatus"; 2286 // Number of messages in the mailbox. 2287 public static final String MESSAGE_COUNT = "messageCount"; 2288 // Number of messages in the mailbox. 2289 public static final String LAST_SEEN_MESSAGE_KEY = "lastSeenMessageKey"; 2290 } 2291 2292 public interface HostAuthColumns { 2293 public static final String ID = "_id"; 2294 // The protocol (e.g. "imap", "pop3", "eas", "smtp" 2295 static final String PROTOCOL = "protocol"; 2296 // The host address 2297 static final String ADDRESS = "address"; 2298 // The port to use for the connection 2299 static final String PORT = "port"; 2300 // General purpose flags 2301 static final String FLAGS = "flags"; 2302 // The login (user name) 2303 static final String LOGIN = "login"; 2304 // Password 2305 static final String PASSWORD = "password"; 2306 // A domain or path, if required (used in IMAP and EAS) 2307 static final String DOMAIN = "domain"; 2308 // DEPRECATED - Will not be set or stored 2309 static final String ACCOUNT_KEY = "accountKey"; 2310 } 2311 2312 public static final class HostAuth extends EmailContent implements HostAuthColumns, Parcelable { 2313 public static final String TABLE_NAME = "HostAuth"; 2314 @SuppressWarnings("hiding") 2315 public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/hostauth"); 2316 2317 public static final int PORT_UNKNOWN = -1; 2318 2319 public static final int FLAG_NONE = 0x00; // No flags 2320 public static final int FLAG_SSL = 0x01; // Use SSL 2321 public static final int FLAG_TLS = 0x02; // Use TLS 2322 public static final int FLAG_AUTHENTICATE = 0x04; // Use name/password for authentication 2323 public static final int FLAG_TRUST_ALL = 0x08; // Trust all certificates 2324 // Mask of settings directly configurable by the user 2325 public static final int USER_CONFIG_MASK = 0x0b; 2326 2327 public String mProtocol; 2328 public String mAddress; 2329 public int mPort; 2330 public int mFlags; 2331 public String mLogin; 2332 public String mPassword; 2333 public String mDomain; 2334 2335 public static final int CONTENT_ID_COLUMN = 0; 2336 public static final int CONTENT_PROTOCOL_COLUMN = 1; 2337 public static final int CONTENT_ADDRESS_COLUMN = 2; 2338 public static final int CONTENT_PORT_COLUMN = 3; 2339 public static final int CONTENT_FLAGS_COLUMN = 4; 2340 public static final int CONTENT_LOGIN_COLUMN = 5; 2341 public static final int CONTENT_PASSWORD_COLUMN = 6; 2342 public static final int CONTENT_DOMAIN_COLUMN = 7; 2343 2344 public static final String[] CONTENT_PROJECTION = new String[] { 2345 RECORD_ID, HostAuthColumns.PROTOCOL, HostAuthColumns.ADDRESS, HostAuthColumns.PORT, 2346 HostAuthColumns.FLAGS, HostAuthColumns.LOGIN, 2347 HostAuthColumns.PASSWORD, HostAuthColumns.DOMAIN 2348 }; 2349 2350 /** 2351 * no public constructor since this is a utility class 2352 */ 2353 public HostAuth() { 2354 mBaseUri = CONTENT_URI; 2355 2356 // other defaults policy) 2357 mPort = PORT_UNKNOWN; 2358 } 2359 2360 /** 2361 * Restore a HostAuth from the database, given its unique id 2362 * @param context 2363 * @param id 2364 * @return the instantiated HostAuth 2365 */ 2366 public static HostAuth restoreHostAuthWithId(Context context, long id) { 2367 return EmailContent.restoreContentWithId(context, HostAuth.class, 2368 HostAuth.CONTENT_URI, HostAuth.CONTENT_PROJECTION, id); 2369 } 2370 2371 2372 /** 2373 * Returns the scheme for the specified flags. 2374 */ 2375 public static String getSchemeString(String protocol, int flags) { 2376 String security = ""; 2377 switch (flags & USER_CONFIG_MASK) { 2378 case FLAG_SSL: 2379 security = "+ssl+"; 2380 break; 2381 case FLAG_SSL | FLAG_TRUST_ALL: 2382 security = "+ssl+trustallcerts"; 2383 break; 2384 case FLAG_TLS: 2385 security = "+tls+"; 2386 break; 2387 case FLAG_TLS | FLAG_TRUST_ALL: 2388 security = "+tls+trustallcerts"; 2389 break; 2390 } 2391 return protocol + security; 2392 } 2393 2394 /** 2395 * Returns the flags for the specified scheme. 2396 */ 2397 public static int getSchemeFlags(String scheme) { 2398 String[] schemeParts = scheme.split("\\+"); 2399 int flags = HostAuth.FLAG_NONE; 2400 if (schemeParts.length >= 2) { 2401 String part1 = schemeParts[1]; 2402 if ("ssl".equals(part1)) { 2403 flags |= HostAuth.FLAG_SSL; 2404 } else if ("tls".equals(part1)) { 2405 flags |= HostAuth.FLAG_TLS; 2406 } 2407 if (schemeParts.length >= 3) { 2408 String part2 = schemeParts[2]; 2409 if ("trustallcerts".equals(part2)) { 2410 flags |= HostAuth.FLAG_TRUST_ALL; 2411 } 2412 } 2413 } 2414 return flags; 2415 } 2416 2417 @Override 2418 public void restore(Cursor cursor) { 2419 mBaseUri = CONTENT_URI; 2420 mId = cursor.getLong(CONTENT_ID_COLUMN); 2421 mProtocol = cursor.getString(CONTENT_PROTOCOL_COLUMN); 2422 mAddress = cursor.getString(CONTENT_ADDRESS_COLUMN); 2423 mPort = cursor.getInt(CONTENT_PORT_COLUMN); 2424 mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN); 2425 mLogin = cursor.getString(CONTENT_LOGIN_COLUMN); 2426 mPassword = cursor.getString(CONTENT_PASSWORD_COLUMN); 2427 mDomain = cursor.getString(CONTENT_DOMAIN_COLUMN); 2428 } 2429 2430 @Override 2431 public ContentValues toContentValues() { 2432 ContentValues values = new ContentValues(); 2433 values.put(HostAuthColumns.PROTOCOL, mProtocol); 2434 values.put(HostAuthColumns.ADDRESS, mAddress); 2435 values.put(HostAuthColumns.PORT, mPort); 2436 values.put(HostAuthColumns.FLAGS, mFlags); 2437 values.put(HostAuthColumns.LOGIN, mLogin); 2438 values.put(HostAuthColumns.PASSWORD, mPassword); 2439 values.put(HostAuthColumns.DOMAIN, mDomain); 2440 values.put(HostAuthColumns.ACCOUNT_KEY, 0); // Need something to satisfy the DB 2441 return values; 2442 } 2443 2444 /** 2445 * For compatibility while converting to provider model, generate a "store URI" 2446 * TODO cache this so we don't rebuild every time 2447 * 2448 * @return a string in the form of a Uri, as used by the other parts of the email app 2449 */ 2450 public String getStoreUri() { 2451 String userInfo = null; 2452 if ((mFlags & FLAG_AUTHENTICATE) != 0) { 2453 String trimUser = (mLogin != null) ? mLogin.trim() : ""; 2454 String password = (mPassword != null) ? mPassword : ""; 2455 userInfo = trimUser + ":" + password; 2456 } 2457 String scheme = getSchemeString(mProtocol, mFlags); 2458 String address = (mAddress != null) ? mAddress.trim() : null; 2459 String path = (mDomain != null) ? "/" + mDomain : null; 2460 2461 URI uri; 2462 try { 2463 uri = new URI( 2464 scheme, 2465 userInfo, 2466 address, 2467 mPort, 2468 path, 2469 null, 2470 null); 2471 return uri.toString(); 2472 } catch (URISyntaxException e) { 2473 return null; 2474 } 2475 } 2476 2477 /** 2478 * Sets the user name and password from URI user info string 2479 */ 2480 public void setLogin(String userInfo) { 2481 String userName = null; 2482 String userPassword = null; 2483 if (!TextUtils.isEmpty(userInfo)) { 2484 String[] userInfoParts = userInfo.split(":", 2); 2485 userName = userInfoParts[0]; 2486 if (userInfoParts.length > 1) { 2487 userPassword = userInfoParts[1]; 2488 } 2489 } 2490 setLogin(userName, userPassword); 2491 } 2492 2493 /** 2494 * Sets the user name and password 2495 */ 2496 public void setLogin(String userName, String userPassword) { 2497 mLogin = userName; 2498 mPassword = userPassword; 2499 2500 if (mLogin == null) { 2501 mFlags &= ~FLAG_AUTHENTICATE; 2502 } else { 2503 mFlags |= FLAG_AUTHENTICATE; 2504 } 2505 } 2506 2507 /** 2508 * Returns the login information. [0] is the username and [1] is the password. If 2509 * {@link #FLAG_AUTHENTICATE} is not set, {@code null} is returned. 2510 */ 2511 public String[] getLogin() { 2512 if ((mFlags & FLAG_AUTHENTICATE) != 0) { 2513 String trimUser = (mLogin != null) ? mLogin.trim() : ""; 2514 String password = (mPassword != null) ? mPassword : ""; 2515 return new String[] { trimUser, password }; 2516 } 2517 return null; 2518 } 2519 2520 /** 2521 * Sets the connection values of the auth structure per the given scheme, host and port. 2522 */ 2523 public void setConnection(String scheme, String host, int port) { 2524 String[] schemeParts = scheme.split("\\+"); 2525 String protocol = schemeParts[0]; 2526 int flags = getSchemeFlags(scheme); 2527 2528 setConnection(protocol, host, port, flags); 2529 } 2530 2531 public void setConnection(String protocol, String address, int port, int flags) { 2532 // Set protocol, security, and additional flags based on uri scheme 2533 mProtocol = protocol; 2534 2535 mFlags &= ~(FLAG_SSL | FLAG_TLS | FLAG_TRUST_ALL); 2536 mFlags |= (flags & USER_CONFIG_MASK); 2537 2538 mAddress = address; 2539 mPort = port; 2540 if (mPort == PORT_UNKNOWN) { 2541 boolean useSSL = ((mFlags & FLAG_SSL) != 0); 2542 // infer port# from protocol + security 2543 // SSL implies a different port - TLS runs in the "regular" port 2544 // NOTE: Although the port should be setup in the various setup screens, this 2545 // block cannot easily be moved because we get process URIs from other sources 2546 // (e.g. for tests, provider templates and account restore) that may or may not 2547 // have a port specified. 2548 if ("pop3".equals(mProtocol)) { 2549 mPort = useSSL ? 995 : 110; 2550 } else if ("imap".equals(mProtocol)) { 2551 mPort = useSSL ? 993 : 143; 2552 } else if ("eas".equals(mProtocol)) { 2553 mPort = useSSL ? 443 : 80; 2554 } else if ("smtp".equals(mProtocol)) { 2555 mPort = useSSL ? 465 : 587; 2556 } 2557 } 2558 } 2559 2560 /** 2561 * Supports Parcelable 2562 */ 2563 @Override 2564 public int describeContents() { 2565 return 0; 2566 } 2567 2568 /** 2569 * Supports Parcelable 2570 */ 2571 public static final Parcelable.Creator<EmailContent.HostAuth> CREATOR 2572 = new Parcelable.Creator<EmailContent.HostAuth>() { 2573 @Override 2574 public EmailContent.HostAuth createFromParcel(Parcel in) { 2575 return new EmailContent.HostAuth(in); 2576 } 2577 2578 @Override 2579 public EmailContent.HostAuth[] newArray(int size) { 2580 return new EmailContent.HostAuth[size]; 2581 } 2582 }; 2583 2584 /** 2585 * Supports Parcelable 2586 */ 2587 @Override 2588 public void writeToParcel(Parcel dest, int flags) { 2589 // mBaseUri is not parceled 2590 dest.writeLong(mId); 2591 dest.writeString(mProtocol); 2592 dest.writeString(mAddress); 2593 dest.writeInt(mPort); 2594 dest.writeInt(mFlags); 2595 dest.writeString(mLogin); 2596 dest.writeString(mPassword); 2597 dest.writeString(mDomain); 2598 } 2599 2600 /** 2601 * Supports Parcelable 2602 */ 2603 public HostAuth(Parcel in) { 2604 mBaseUri = CONTENT_URI; 2605 mId = in.readLong(); 2606 mProtocol = in.readString(); 2607 mAddress = in.readString(); 2608 mPort = in.readInt(); 2609 mFlags = in.readInt(); 2610 mLogin = in.readString(); 2611 mPassword = in.readString(); 2612 mDomain = in.readString(); 2613 } 2614 2615 /** 2616 * For debugger support only - DO NOT use for code. 2617 */ 2618 @Override 2619 public String toString() { 2620 return getStoreUri(); 2621 } 2622 2623 @Override 2624 public boolean equals(Object o) { 2625 if (!(o instanceof HostAuth)) { 2626 return false; 2627 } 2628 HostAuth that = (HostAuth)o; 2629 return mPort == that.mPort 2630 && mFlags == that.mFlags 2631 && Utility.areStringsEqual(mProtocol, that.mProtocol) 2632 && Utility.areStringsEqual(mAddress, that.mAddress) 2633 && Utility.areStringsEqual(mLogin, that.mLogin) 2634 && Utility.areStringsEqual(mPassword, that.mPassword) 2635 && Utility.areStringsEqual(mDomain, that.mDomain); 2636 } 2637 } 2638 2639 public interface PolicyColumns { 2640 public static final String ID = "_id"; 2641 public static final String PASSWORD_MODE = "passwordMode"; 2642 public static final String PASSWORD_MIN_LENGTH = "passwordMinLength"; 2643 public static final String PASSWORD_EXPIRATION_DAYS = "passwordExpirationDays"; 2644 public static final String PASSWORD_HISTORY = "passwordHistory"; 2645 public static final String PASSWORD_COMPLEX_CHARS = "passwordComplexChars"; 2646 public static final String PASSWORD_MAX_FAILS = "passwordMaxFails"; 2647 public static final String MAX_SCREEN_LOCK_TIME = "maxScreenLockTime"; 2648 public static final String REQUIRE_REMOTE_WIPE = "requireRemoteWipe"; 2649 public static final String REQUIRE_ENCRYPTION = "requireEncryption"; 2650 public static final String REQUIRE_ENCRYPTION_EXTERNAL = "requireEncryptionExternal"; 2651 // ICS additions 2652 // Note: the appearance of these columns does not imply that we support these features; only 2653 // that we store them in the Policy structure 2654 public static final String REQUIRE_MANUAL_SYNC_WHEN_ROAMING = "requireManualSyncRoaming"; 2655 public static final String DONT_ALLOW_CAMERA = "dontAllowCamera"; 2656 public static final String DONT_ALLOW_ATTACHMENTS = "dontAllowAttachments"; 2657 public static final String DONT_ALLOW_HTML = "dontAllowHtml"; 2658 public static final String MAX_ATTACHMENT_SIZE = "maxAttachmentSize"; 2659 public static final String MAX_TEXT_TRUNCATION_SIZE = "maxTextTruncationSize"; 2660 public static final String MAX_HTML_TRUNCATION_SIZE = "maxHTMLTruncationSize"; 2661 public static final String MAX_EMAIL_LOOKBACK = "maxEmailLookback"; 2662 public static final String MAX_CALENDAR_LOOKBACK = "maxCalendarLookback"; 2663 // Indicates that the server allows password recovery, not that we support it 2664 public static final String PASSWORD_RECOVERY_ENABLED = "passwordRecoveryEnabled"; 2665 } 2666} 2667