Account.java revision 4de538be2d17545fb63e781412b8565f0d0d97d4
1package com.android.emailcommon.provider; 2 3import android.content.ContentProviderOperation; 4import android.content.ContentProviderResult; 5import android.content.ContentResolver; 6import android.content.ContentUris; 7import android.content.ContentValues; 8import android.content.Context; 9import android.content.OperationApplicationException; 10import android.database.Cursor; 11import android.net.Uri; 12import android.os.Parcel; 13import android.os.Parcelable; 14import android.os.RemoteException; 15 16import com.android.emailcommon.provider.EmailContent.AccountColumns; 17import com.android.emailcommon.utility.Utility; 18 19import java.util.ArrayList; 20import java.util.List; 21import java.util.UUID; 22 23public final class Account extends EmailContent implements AccountColumns, Parcelable { 24 public static final String TABLE_NAME = "Account"; 25 @SuppressWarnings("hiding") 26 public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/account"); 27 public static final Uri ADD_TO_FIELD_URI = 28 Uri.parse(EmailContent.CONTENT_URI + "/accountIdAddToField"); 29 public static final Uri RESET_NEW_MESSAGE_COUNT_URI = 30 Uri.parse(EmailContent.CONTENT_URI + "/resetNewMessageCount"); 31 public static final Uri NOTIFIER_URI = 32 Uri.parse(EmailContent.CONTENT_NOTIFIER_URI + "/account"); 33 public static final Uri DEFAULT_ACCOUNT_ID_URI = 34 Uri.parse(EmailContent.CONTENT_URI + "/account/default"); 35 36 // Define all pseudo account IDs here to avoid conflict with one another. 37 /** 38 * Pseudo account ID to represent a "combined account" that includes messages and mailboxes 39 * from all defined accounts. 40 * 41 * <em>IMPORTANT</em>: This must never be stored to the database. 42 */ 43 public static final long ACCOUNT_ID_COMBINED_VIEW = 0x1000000000000000L; 44 /** 45 * Pseudo account ID to represent "no account". This may be used any time the account ID 46 * may not be known or when we want to specifically select "no" account. 47 * 48 * <em>IMPORTANT</em>: This must never be stored to the database. 49 */ 50 public static final long NO_ACCOUNT = -1L; 51 52 // Whether or not the user has asked for notifications of new mail in this account 53 public final static int FLAGS_NOTIFY_NEW_MAIL = 1<<0; 54 // Whether or not the user has asked for vibration notifications with all new mail 55 public final static int FLAGS_VIBRATE_ALWAYS = 1<<1; 56 // Bit mask for the account's deletion policy (see DELETE_POLICY_x below) 57 public static final int FLAGS_DELETE_POLICY_MASK = 1<<2 | 1<<3; 58 public static final int FLAGS_DELETE_POLICY_SHIFT = 2; 59 // Whether the account is in the process of being created; any account reconciliation code 60 // MUST ignore accounts with this bit set; in addition, ContentObservers for this data 61 // SHOULD consider the state of this flag during operation 62 public static final int FLAGS_INCOMPLETE = 1<<4; 63 // Security hold is used when the device is not in compliance with security policies 64 // required by the server; in this state, the user MUST be alerted to the need to update 65 // security settings. Sync adapters SHOULD NOT attempt to sync when this flag is set. 66 public static final int FLAGS_SECURITY_HOLD = 1<<5; 67 // Whether or not the user has asked for vibration notifications when the ringer is silent 68 public static final int FLAGS_VIBRATE_WHEN_SILENT = 1<<6; 69 // Whether the account supports "smart forward" (i.e. the server appends the original 70 // message along with any attachments to the outgoing message) 71 public static final int FLAGS_SUPPORTS_SMART_FORWARD = 1<<7; 72 // Whether the account should try to cache attachments in the background 73 public static final int FLAGS_BACKGROUND_ATTACHMENTS = 1<<8; 74 // Available to sync adapter 75 public static final int FLAGS_SYNC_ADAPTER = 1<<9; 76 // Sync disabled is a status commanded by the server; the sync adapter SHOULD NOT try to 77 // sync mailboxes in this account automatically. A manual sync request to sync a mailbox 78 // with sync disabled SHOULD try to sync and report any failure result via the UI. 79 public static final int FLAGS_SYNC_DISABLED = 1<<10; 80 // Whether or not server-side search is supported by this account 81 public static final int FLAGS_SUPPORTS_SEARCH = 1<<11; 82 // Whether or not server-side search supports global search (i.e. all mailboxes); only valid 83 // if FLAGS_SUPPORTS_SEARCH is true 84 public static final int FLAGS_SUPPORTS_GLOBAL_SEARCH = 1<<12; 85 86 // Deletion policy (see FLAGS_DELETE_POLICY_MASK, above) 87 public static final int DELETE_POLICY_NEVER = 0; 88 public static final int DELETE_POLICY_7DAYS = 1<<0; // not supported 89 public static final int DELETE_POLICY_ON_DELETE = 1<<1; 90 91 // Sentinel values for the mSyncInterval field of both Account records 92 public static final int CHECK_INTERVAL_NEVER = -1; 93 public static final int CHECK_INTERVAL_PUSH = -2; 94 95 public String mDisplayName; 96 public String mEmailAddress; 97 public String mSyncKey; 98 public int mSyncLookback; 99 public int mSyncInterval; 100 public long mHostAuthKeyRecv; 101 public long mHostAuthKeySend; 102 public int mFlags; 103 public boolean mIsDefault; // note: callers should use getDefaultAccountId() 104 public String mCompatibilityUuid; 105 public String mSenderName; 106 public String mRingtoneUri; 107 public String mProtocolVersion; 108 public int mNewMessageCount; 109 public String mSecuritySyncKey; 110 public String mSignature; 111 public long mPolicyKey; 112 113 // Convenience for creating/working with an account 114 public transient HostAuth mHostAuthRecv; 115 public transient HostAuth mHostAuthSend; 116 public transient Policy mPolicy; 117 // Might hold the corresponding AccountManager account structure 118 public transient android.accounts.Account mAmAccount; 119 120 public static final int CONTENT_ID_COLUMN = 0; 121 public static final int CONTENT_DISPLAY_NAME_COLUMN = 1; 122 public static final int CONTENT_EMAIL_ADDRESS_COLUMN = 2; 123 public static final int CONTENT_SYNC_KEY_COLUMN = 3; 124 public static final int CONTENT_SYNC_LOOKBACK_COLUMN = 4; 125 public static final int CONTENT_SYNC_INTERVAL_COLUMN = 5; 126 public static final int CONTENT_HOST_AUTH_KEY_RECV_COLUMN = 6; 127 public static final int CONTENT_HOST_AUTH_KEY_SEND_COLUMN = 7; 128 public static final int CONTENT_FLAGS_COLUMN = 8; 129 public static final int CONTENT_IS_DEFAULT_COLUMN = 9; 130 public static final int CONTENT_COMPATIBILITY_UUID_COLUMN = 10; 131 public static final int CONTENT_SENDER_NAME_COLUMN = 11; 132 public static final int CONTENT_RINGTONE_URI_COLUMN = 12; 133 public static final int CONTENT_PROTOCOL_VERSION_COLUMN = 13; 134 public static final int CONTENT_NEW_MESSAGE_COUNT_COLUMN = 14; 135 public static final int CONTENT_SECURITY_SYNC_KEY_COLUMN = 15; 136 public static final int CONTENT_SIGNATURE_COLUMN = 16; 137 public static final int CONTENT_POLICY_KEY = 17; 138 139 public static final String[] CONTENT_PROJECTION = new String[] { 140 RECORD_ID, AccountColumns.DISPLAY_NAME, 141 AccountColumns.EMAIL_ADDRESS, AccountColumns.SYNC_KEY, AccountColumns.SYNC_LOOKBACK, 142 AccountColumns.SYNC_INTERVAL, AccountColumns.HOST_AUTH_KEY_RECV, 143 AccountColumns.HOST_AUTH_KEY_SEND, AccountColumns.FLAGS, AccountColumns.IS_DEFAULT, 144 AccountColumns.COMPATIBILITY_UUID, AccountColumns.SENDER_NAME, 145 AccountColumns.RINGTONE_URI, AccountColumns.PROTOCOL_VERSION, 146 AccountColumns.NEW_MESSAGE_COUNT, AccountColumns.SECURITY_SYNC_KEY, 147 AccountColumns.SIGNATURE, AccountColumns.POLICY_KEY 148 }; 149 150 public static final int CONTENT_MAILBOX_TYPE_COLUMN = 1; 151 152 /** 153 * This projection is for listing account id's only 154 */ 155 public static final String[] ID_TYPE_PROJECTION = new String[] { 156 RECORD_ID, MailboxColumns.TYPE 157 }; 158 159 public static final int ACCOUNT_FLAGS_COLUMN_ID = 0; 160 public static final int ACCOUNT_FLAGS_COLUMN_FLAGS = 1; 161 public static final String[] ACCOUNT_FLAGS_PROJECTION = new String[] { 162 AccountColumns.ID, AccountColumns.FLAGS}; 163 164 public static final String MAILBOX_SELECTION = 165 MessageColumns.MAILBOX_KEY + " =?"; 166 167 public static final String UNREAD_COUNT_SELECTION = 168 MessageColumns.MAILBOX_KEY + " =? and " + MessageColumns.FLAG_READ + "= 0"; 169 170 private static final String UUID_SELECTION = AccountColumns.COMPATIBILITY_UUID + " =?"; 171 172 public static final String SECURITY_NONZERO_SELECTION = 173 Account.POLICY_KEY + " IS NOT NULL AND " + Account.POLICY_KEY + "!=0"; 174 175 private static final String FIND_INBOX_SELECTION = 176 MailboxColumns.TYPE + " = " + Mailbox.TYPE_INBOX + 177 " AND " + MailboxColumns.ACCOUNT_KEY + " =?"; 178 179 /** 180 * This projection is for searching for the default account 181 */ 182 private static final String[] DEFAULT_ID_PROJECTION = new String[] { 183 RECORD_ID, IS_DEFAULT 184 }; 185 186 /** 187 * no public constructor since this is a utility class 188 */ 189 public Account() { 190 mBaseUri = CONTENT_URI; 191 192 // other defaults (policy) 193 mRingtoneUri = "content://settings/system/notification_sound"; 194 mSyncInterval = -1; 195 mSyncLookback = -1; 196 mFlags = FLAGS_NOTIFY_NEW_MAIL; 197 mCompatibilityUuid = UUID.randomUUID().toString(); 198 } 199 200 public static Account restoreAccountWithId(Context context, long id) { 201 return EmailContent.restoreContentWithId(context, Account.class, 202 Account.CONTENT_URI, Account.CONTENT_PROJECTION, id); 203 } 204 205 /** 206 * Returns {@code true} if the given account ID is a "normal" account. Normal accounts 207 * always have an ID greater than {@code 0} and not equal to any pseudo account IDs 208 * (such as {@link #ACCOUNT_ID_COMBINED_VIEW}) 209 */ 210 public static boolean isNormalAccount(long accountId) { 211 return (accountId > 0L) && (accountId != ACCOUNT_ID_COMBINED_VIEW); 212 } 213 214 /** 215 * Refresh an account that has already been loaded. This is slightly less expensive 216 * that generating a brand-new account object. 217 */ 218 public void refresh(Context context) { 219 Cursor c = context.getContentResolver().query(getUri(), Account.CONTENT_PROJECTION, 220 null, null, null); 221 try { 222 c.moveToFirst(); 223 restore(c); 224 } finally { 225 if (c != null) { 226 c.close(); 227 } 228 } 229 } 230 231 @Override 232 public void restore(Cursor cursor) { 233 mId = cursor.getLong(CONTENT_ID_COLUMN); 234 mBaseUri = CONTENT_URI; 235 mDisplayName = cursor.getString(CONTENT_DISPLAY_NAME_COLUMN); 236 mEmailAddress = cursor.getString(CONTENT_EMAIL_ADDRESS_COLUMN); 237 mSyncKey = cursor.getString(CONTENT_SYNC_KEY_COLUMN); 238 mSyncLookback = cursor.getInt(CONTENT_SYNC_LOOKBACK_COLUMN); 239 mSyncInterval = cursor.getInt(CONTENT_SYNC_INTERVAL_COLUMN); 240 mHostAuthKeyRecv = cursor.getLong(CONTENT_HOST_AUTH_KEY_RECV_COLUMN); 241 mHostAuthKeySend = cursor.getLong(CONTENT_HOST_AUTH_KEY_SEND_COLUMN); 242 mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN); 243 mIsDefault = cursor.getInt(CONTENT_IS_DEFAULT_COLUMN) == 1; 244 mCompatibilityUuid = cursor.getString(CONTENT_COMPATIBILITY_UUID_COLUMN); 245 mSenderName = cursor.getString(CONTENT_SENDER_NAME_COLUMN); 246 mRingtoneUri = cursor.getString(CONTENT_RINGTONE_URI_COLUMN); 247 mProtocolVersion = cursor.getString(CONTENT_PROTOCOL_VERSION_COLUMN); 248 mNewMessageCount = cursor.getInt(CONTENT_NEW_MESSAGE_COUNT_COLUMN); 249 mSecuritySyncKey = cursor.getString(CONTENT_SECURITY_SYNC_KEY_COLUMN); 250 mSignature = cursor.getString(CONTENT_SIGNATURE_COLUMN); 251 mPolicyKey = cursor.getLong(CONTENT_POLICY_KEY); 252 } 253 254 private long getId(Uri u) { 255 return Long.parseLong(u.getPathSegments().get(1)); 256 } 257 258 /** 259 * @return the user-visible name for the account 260 */ 261 public String getDisplayName() { 262 return mDisplayName; 263 } 264 265 /** 266 * Set the description. Be sure to call save() to commit to database. 267 * @param description the new description 268 */ 269 public void setDisplayName(String description) { 270 mDisplayName = description; 271 } 272 273 /** 274 * @return the email address for this account 275 */ 276 public String getEmailAddress() { 277 return mEmailAddress; 278 } 279 280 /** 281 * Set the Email address for this account. Be sure to call save() to commit to database. 282 * @param emailAddress the new email address for this account 283 */ 284 public void setEmailAddress(String emailAddress) { 285 mEmailAddress = emailAddress; 286 } 287 288 /** 289 * @return the sender's name for this account 290 */ 291 public String getSenderName() { 292 return mSenderName; 293 } 294 295 /** 296 * Set the sender's name. Be sure to call save() to commit to database. 297 * @param name the new sender name 298 */ 299 public void setSenderName(String name) { 300 mSenderName = name; 301 } 302 303 public String getSignature() { 304 return mSignature; 305 } 306 307 public void setSignature(String signature) { 308 mSignature = signature; 309 } 310 311 /** 312 * @return the minutes per check (for polling) 313 * TODO define sentinel values for "never", "push", etc. See Account.java 314 */ 315 public int getSyncInterval() { 316 return mSyncInterval; 317 } 318 319 /** 320 * Set the minutes per check (for polling). Be sure to call save() to commit to database. 321 * TODO define sentinel values for "never", "push", etc. See Account.java 322 * @param minutes the number of minutes between polling checks 323 */ 324 public void setSyncInterval(int minutes) { 325 mSyncInterval = minutes; 326 } 327 328 /** 329 * @return One of the {@code Account.SYNC_WINDOW_*} constants that represents the sync 330 * lookback window. 331 * TODO define sentinel values for "all", "1 month", etc. See Account.java 332 */ 333 public int getSyncLookback() { 334 return mSyncLookback; 335 } 336 337 /** 338 * Set the sync lookback window. Be sure to call save() to commit to database. 339 * TODO define sentinel values for "all", "1 month", etc. See Account.java 340 * @param value One of the {@link com.android.emailcommon.service.SyncWindow} constants 341 */ 342 public void setSyncLookback(int value) { 343 mSyncLookback = value; 344 } 345 346 /** 347 * @return the flags for this account 348 * @see #FLAGS_NOTIFY_NEW_MAIL 349 * @see #FLAGS_VIBRATE_ALWAYS 350 * @see #FLAGS_VIBRATE_WHEN_SILENT 351 */ 352 public int getFlags() { 353 return mFlags; 354 } 355 356 /** 357 * Set the flags for this account 358 * @see #FLAGS_NOTIFY_NEW_MAIL 359 * @see #FLAGS_VIBRATE_ALWAYS 360 * @see #FLAGS_VIBRATE_WHEN_SILENT 361 * @param newFlags the new value for the flags 362 */ 363 public void setFlags(int newFlags) { 364 mFlags = newFlags; 365 } 366 367 /** 368 * @return the ringtone Uri for this account 369 */ 370 public String getRingtone() { 371 return mRingtoneUri; 372 } 373 374 /** 375 * Set the ringtone Uri for this account 376 * @param newUri the new URI string for the ringtone for this account 377 */ 378 public void setRingtone(String newUri) { 379 mRingtoneUri = newUri; 380 } 381 382 /** 383 * Set the "delete policy" as a simple 0,1,2 value set. 384 * @param newPolicy the new delete policy 385 */ 386 public void setDeletePolicy(int newPolicy) { 387 mFlags &= ~FLAGS_DELETE_POLICY_MASK; 388 mFlags |= (newPolicy << FLAGS_DELETE_POLICY_SHIFT) & FLAGS_DELETE_POLICY_MASK; 389 } 390 391 /** 392 * Return the "delete policy" as a simple 0,1,2 value set. 393 * @return the current delete policy 394 */ 395 public int getDeletePolicy() { 396 return (mFlags & FLAGS_DELETE_POLICY_MASK) >> FLAGS_DELETE_POLICY_SHIFT; 397 } 398 399 /** 400 * Return the Uuid associated with this account. This is primarily for compatibility 401 * with accounts set up by previous versions, because there are externals references 402 * to the Uuid (e.g. desktop shortcuts). 403 */ 404 public String getUuid() { 405 return mCompatibilityUuid; 406 } 407 408 public HostAuth getOrCreateHostAuthSend(Context context) { 409 if (mHostAuthSend == null) { 410 if (mHostAuthKeySend != 0) { 411 mHostAuthSend = HostAuth.restoreHostAuthWithId(context, mHostAuthKeySend); 412 } else { 413 mHostAuthSend = new HostAuth(); 414 } 415 } 416 return mHostAuthSend; 417 } 418 419 public HostAuth getOrCreateHostAuthRecv(Context context) { 420 if (mHostAuthRecv == null) { 421 if (mHostAuthKeyRecv != 0) { 422 mHostAuthRecv = HostAuth.restoreHostAuthWithId(context, mHostAuthKeyRecv); 423 } else { 424 mHostAuthRecv = new HostAuth(); 425 } 426 } 427 return mHostAuthRecv; 428 } 429 430 /** 431 * For compatibility while converting to provider model, generate a "local store URI" 432 * 433 * @return a string in the form of a Uri, as used by the other parts of the email app 434 */ 435 public String getLocalStoreUri(Context context) { 436 return "local://localhost/" + context.getDatabasePath(getUuid() + ".db"); 437 } 438 439 /** 440 * @return true if the instance is of an EAS account. 441 * 442 * NOTE This method accesses the DB if {@link #mHostAuthRecv} hasn't been restored yet. 443 * Use caution when you use this on the main thread. 444 */ 445 public boolean isEasAccount(Context context) { 446 return "eas".equals(getProtocol(context)); 447 } 448 449 /** 450 * @return true if the account supports "move messages". 451 */ 452 public static boolean supportsMoveMessages(Context context, long accountId) { 453 String protocol = getProtocol(context, accountId); 454 return "eas".equals(protocol) || "imap".equals(protocol); 455 } 456 457 /** 458 * @return true if the account supports "search". 459 */ 460 public static boolean supportsServerSearch(Context context, long accountId) { 461 Account account = Account.restoreAccountWithId(context, accountId); 462 if (account == null) return false; 463 return (account.mFlags & Account.FLAGS_SUPPORTS_SEARCH) != 0; 464 } 465 466 /** 467 * Set the account to be the default account. If this is set to "true", when the account 468 * is saved, all other accounts will have the same value set to "false". 469 * @param newDefaultState the new default state - if true, others will be cleared. 470 */ 471 public void setDefaultAccount(boolean newDefaultState) { 472 mIsDefault = newDefaultState; 473 } 474 475 /** 476 * Helper method for finding the default account. 477 */ 478 static private long getDefaultAccountWhere(Context context, String where) { 479 return Utility.getFirstRowLong(context, CONTENT_URI, 480 DEFAULT_ID_PROJECTION, 481 where, null, null, 0, Long.valueOf(-1)); 482 } 483 484 /** 485 * @return {@link Uri} to this {@link Account} in the 486 * {@code content://com.android.email.provider/account/UUID} format, which is safe to use 487 * for desktop shortcuts. 488 * 489 * <p>We don't want to store _id in shortcuts, because 490 * {@link com.android.email.provider.AccountBackupRestore} won't preserve it. 491 */ 492 public Uri getShortcutSafeUri() { 493 return getShortcutSafeUriFromUuid(mCompatibilityUuid); 494 } 495 496 /** 497 * @return {@link Uri} to an {@link Account} with a {@code uuid}. 498 */ 499 public static Uri getShortcutSafeUriFromUuid(String uuid) { 500 return CONTENT_URI.buildUpon().appendEncodedPath(uuid).build(); 501 } 502 503 /** 504 * Parse {@link Uri} in the {@code content://com.android.email.provider/account/ID} format 505 * where ID = account id (used on Eclair, Android 2.0-2.1) or UUID, and return _id of 506 * the {@link Account} associated with it. 507 * 508 * @param context context to access DB 509 * @param uri URI of interest 510 * @return _id of the {@link Account} associated with ID, or -1 if none found. 511 */ 512 public static long getAccountIdFromShortcutSafeUri(Context context, Uri uri) { 513 // Make sure the URI is in the correct format. 514 if (!"content".equals(uri.getScheme()) 515 || !AUTHORITY.equals(uri.getAuthority())) { 516 return -1; 517 } 518 519 final List<String> ps = uri.getPathSegments(); 520 if (ps.size() != 2 || !"account".equals(ps.get(0))) { 521 return -1; 522 } 523 524 // Now get the ID part. 525 final String id = ps.get(1); 526 527 // First, see if ID can be parsed as long. (Eclair-style) 528 // (UUIDs have '-' in them, so they are always non-parsable.) 529 try { 530 return Long.parseLong(id); 531 } catch (NumberFormatException ok) { 532 // OK, it's not a long. Continue... 533 } 534 535 // Now id is a UUId. 536 return getAccountIdFromUuid(context, id); 537 } 538 539 /** 540 * @return ID of the account with the given UUID. 541 */ 542 public static long getAccountIdFromUuid(Context context, String uuid) { 543 return Utility.getFirstRowLong(context, 544 CONTENT_URI, ID_PROJECTION, 545 UUID_SELECTION, new String[] {uuid}, null, 0, -1L); 546 } 547 548 /** 549 * Return the id of the default account. If one hasn't been explicitly specified, return 550 * the first one in the database (the logic is provided within EmailProvider) 551 * @param context the caller's context 552 * @return the id of the default account, or Account.NO_ACCOUNT if there are no accounts 553 */ 554 static public long getDefaultAccountId(Context context) { 555 Cursor c = context.getContentResolver().query( 556 Account.DEFAULT_ACCOUNT_ID_URI, Account.ID_PROJECTION, null, null, null); 557 try { 558 if (c != null && c.moveToFirst()) { 559 return c.getLong(Account.ID_PROJECTION_COLUMN); 560 } 561 } finally { 562 c.close(); 563 } 564 return Account.NO_ACCOUNT; 565 } 566 567 /** 568 * Given an account id, return the account's protocol 569 * @param context the caller's context 570 * @param accountId the id of the account to be examined 571 * @return the account's protocol (or null if the Account or HostAuth do not exist) 572 */ 573 public static String getProtocol(Context context, long accountId) { 574 Account account = Account.restoreAccountWithId(context, accountId); 575 if (account != null) { 576 return account.getProtocol(context); 577 } 578 return null; 579 } 580 581 /** 582 * Return the account's protocol 583 * @param context the caller's context 584 * @return the account's protocol (or null if the HostAuth doesn't not exist) 585 */ 586 public String getProtocol(Context context) { 587 HostAuth hostAuth = HostAuth.restoreHostAuthWithId(context, mHostAuthKeyRecv); 588 if (hostAuth != null) { 589 return hostAuth.mProtocol; 590 } 591 return null; 592 } 593 594 /** 595 * Return the account ID for a message with a given id 596 * 597 * @param context the caller's context 598 * @param messageId the id of the message 599 * @return the account ID, or -1 if the account doesn't exist 600 */ 601 public static long getAccountIdForMessageId(Context context, long messageId) { 602 return Message.getKeyColumnLong(context, messageId, MessageColumns.ACCOUNT_KEY); 603 } 604 605 /** 606 * Return the account for a message with a given id 607 * @param context the caller's context 608 * @param messageId the id of the message 609 * @return the account, or null if the account doesn't exist 610 */ 611 public static Account getAccountForMessageId(Context context, long messageId) { 612 long accountId = getAccountIdForMessageId(context, messageId); 613 if (accountId != -1) { 614 return Account.restoreAccountWithId(context, accountId); 615 } 616 return null; 617 } 618 619 /** 620 * @return true if an {@code accountId} is assigned to any existing account. 621 */ 622 public static boolean isValidId(Context context, long accountId) { 623 return null != Utility.getFirstRowLong(context, CONTENT_URI, ID_PROJECTION, 624 ID_SELECTION, new String[] {Long.toString(accountId)}, null, 625 ID_PROJECTION_COLUMN); 626 } 627 628 /** 629 * Check a single account for security hold status. 630 */ 631 public static boolean isSecurityHold(Context context, long accountId) { 632 return (Utility.getFirstRowLong(context, 633 ContentUris.withAppendedId(Account.CONTENT_URI, accountId), 634 ACCOUNT_FLAGS_PROJECTION, null, null, null, ACCOUNT_FLAGS_COLUMN_FLAGS, 0L) 635 & Account.FLAGS_SECURITY_HOLD) != 0; 636 } 637 638 /** 639 * @return id of the "inbox" mailbox, or -1 if not found. 640 */ 641 public static long getInboxId(Context context, long accountId) { 642 return Utility.getFirstRowLong(context, Mailbox.CONTENT_URI, ID_PROJECTION, 643 FIND_INBOX_SELECTION, new String[] {Long.toString(accountId)}, null, 644 ID_PROJECTION_COLUMN, -1L); 645 } 646 647 /** 648 * Clear all account hold flags that are set. 649 * 650 * (This will trigger watchers, and in particular will cause EAS to try and resync the 651 * account(s).) 652 */ 653 public static void clearSecurityHoldOnAllAccounts(Context context) { 654 ContentResolver resolver = context.getContentResolver(); 655 Cursor c = resolver.query(Account.CONTENT_URI, ACCOUNT_FLAGS_PROJECTION, 656 SECURITY_NONZERO_SELECTION, null, null); 657 try { 658 while (c.moveToNext()) { 659 int flags = c.getInt(ACCOUNT_FLAGS_COLUMN_FLAGS); 660 661 if (0 != (flags & FLAGS_SECURITY_HOLD)) { 662 ContentValues cv = new ContentValues(); 663 cv.put(AccountColumns.FLAGS, flags & ~FLAGS_SECURITY_HOLD); 664 long accountId = c.getLong(ACCOUNT_FLAGS_COLUMN_ID); 665 Uri uri = ContentUris.withAppendedId(Account.CONTENT_URI, accountId); 666 resolver.update(uri, cv, null, null); 667 } 668 } 669 } finally { 670 c.close(); 671 } 672 } 673 674 /** 675 * Override update to enforce a single default account, and do it atomically 676 */ 677 @Override 678 public int update(Context context, ContentValues cv) { 679 if (mPolicy != null && mPolicyKey <= 0) { 680 // If a policy is set and there's no policy, link it to the account 681 Policy.setAccountPolicy(context, this, mPolicy, null); 682 } 683 if (cv.containsKey(AccountColumns.IS_DEFAULT) && 684 cv.getAsBoolean(AccountColumns.IS_DEFAULT)) { 685 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); 686 ContentValues cv1 = new ContentValues(); 687 cv1.put(AccountColumns.IS_DEFAULT, false); 688 // Clear the default flag in all accounts 689 ops.add(ContentProviderOperation.newUpdate(CONTENT_URI).withValues(cv1).build()); 690 // Update this account 691 ops.add(ContentProviderOperation 692 .newUpdate(ContentUris.withAppendedId(CONTENT_URI, mId)) 693 .withValues(cv).build()); 694 try { 695 context.getContentResolver().applyBatch(AUTHORITY, ops); 696 return 1; 697 } catch (RemoteException e) { 698 // There is nothing to be done here; fail by returning 0 699 } catch (OperationApplicationException e) { 700 // There is nothing to be done here; fail by returning 0 701 } 702 return 0; 703 } 704 return super.update(context, cv); 705 } 706 707 /* 708 * Override this so that we can store the HostAuth's first and link them to the Account 709 * (non-Javadoc) 710 * @see com.android.email.provider.EmailContent#save(android.content.Context) 711 */ 712 @Override 713 public Uri save(Context context) { 714 if (isSaved()) { 715 throw new UnsupportedOperationException(); 716 } 717 // This logic is in place so I can (a) short circuit the expensive stuff when 718 // possible, and (b) override (and throw) if anyone tries to call save() or update() 719 // directly for Account, which are unsupported. 720 if (mHostAuthRecv == null && mHostAuthSend == null && mIsDefault == false && 721 mPolicy != null) { 722 return super.save(context); 723 } 724 725 int index = 0; 726 int recvIndex = -1; 727 int sendIndex = -1; 728 int policyIndex = -1; 729 730 // Create operations for saving the send and recv hostAuths 731 // Also, remember which operation in the array they represent 732 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); 733 if (mHostAuthRecv != null) { 734 recvIndex = index++; 735 ops.add(ContentProviderOperation.newInsert(mHostAuthRecv.mBaseUri) 736 .withValues(mHostAuthRecv.toContentValues()) 737 .build()); 738 } 739 if (mHostAuthSend != null) { 740 sendIndex = index++; 741 ops.add(ContentProviderOperation.newInsert(mHostAuthSend.mBaseUri) 742 .withValues(mHostAuthSend.toContentValues()) 743 .build()); 744 } 745 if (mPolicy != null) { 746 policyIndex = index++; 747 ops.add(ContentProviderOperation.newInsert(mPolicy.mBaseUri) 748 .withValues(mPolicy.toContentValues()) 749 .build()); 750 } 751 752 // Create operations for making this the only default account 753 // Note, these are always updates because they change existing accounts 754 if (mIsDefault) { 755 index++; 756 ContentValues cv1 = new ContentValues(); 757 cv1.put(AccountColumns.IS_DEFAULT, 0); 758 ops.add(ContentProviderOperation.newUpdate(CONTENT_URI).withValues(cv1).build()); 759 } 760 761 // Now do the Account 762 ContentValues cv = null; 763 if (recvIndex >= 0 || sendIndex >= 0 || policyIndex >= 0) { 764 cv = new ContentValues(); 765 if (recvIndex >= 0) { 766 cv.put(Account.HOST_AUTH_KEY_RECV, recvIndex); 767 } 768 if (sendIndex >= 0) { 769 cv.put(Account.HOST_AUTH_KEY_SEND, sendIndex); 770 } 771 if (policyIndex >= 0) { 772 cv.put(Account.POLICY_KEY, policyIndex); 773 } 774 } 775 776 ContentProviderOperation.Builder b = ContentProviderOperation.newInsert(mBaseUri); 777 b.withValues(toContentValues()); 778 if (cv != null) { 779 b.withValueBackReferences(cv); 780 } 781 ops.add(b.build()); 782 783 try { 784 ContentProviderResult[] results = 785 context.getContentResolver().applyBatch(AUTHORITY, ops); 786 // If saving, set the mId's of the various saved objects 787 if (recvIndex >= 0) { 788 long newId = getId(results[recvIndex].uri); 789 mHostAuthKeyRecv = newId; 790 mHostAuthRecv.mId = newId; 791 } 792 if (sendIndex >= 0) { 793 long newId = getId(results[sendIndex].uri); 794 mHostAuthKeySend = newId; 795 mHostAuthSend.mId = newId; 796 } 797 if (policyIndex >= 0) { 798 long newId = getId(results[policyIndex].uri); 799 mPolicyKey = newId; 800 mPolicy.mId = newId; 801 } 802 Uri u = results[index].uri; 803 mId = getId(u); 804 return u; 805 } catch (RemoteException e) { 806 // There is nothing to be done here; fail by returning null 807 } catch (OperationApplicationException e) { 808 // There is nothing to be done here; fail by returning null 809 } 810 return null; 811 } 812 813 @Override 814 public ContentValues toContentValues() { 815 ContentValues values = new ContentValues(); 816 values.put(AccountColumns.DISPLAY_NAME, mDisplayName); 817 values.put(AccountColumns.EMAIL_ADDRESS, mEmailAddress); 818 values.put(AccountColumns.SYNC_KEY, mSyncKey); 819 values.put(AccountColumns.SYNC_LOOKBACK, mSyncLookback); 820 values.put(AccountColumns.SYNC_INTERVAL, mSyncInterval); 821 values.put(AccountColumns.HOST_AUTH_KEY_RECV, mHostAuthKeyRecv); 822 values.put(AccountColumns.HOST_AUTH_KEY_SEND, mHostAuthKeySend); 823 values.put(AccountColumns.FLAGS, mFlags); 824 values.put(AccountColumns.IS_DEFAULT, mIsDefault); 825 values.put(AccountColumns.COMPATIBILITY_UUID, mCompatibilityUuid); 826 values.put(AccountColumns.SENDER_NAME, mSenderName); 827 values.put(AccountColumns.RINGTONE_URI, mRingtoneUri); 828 values.put(AccountColumns.PROTOCOL_VERSION, mProtocolVersion); 829 values.put(AccountColumns.NEW_MESSAGE_COUNT, mNewMessageCount); 830 values.put(AccountColumns.SECURITY_SYNC_KEY, mSecuritySyncKey); 831 values.put(AccountColumns.SIGNATURE, mSignature); 832 values.put(AccountColumns.POLICY_KEY, mPolicyKey); 833 return values; 834 } 835 836 /** 837 * Supports Parcelable 838 */ 839 @Override 840 public int describeContents() { 841 return 0; 842 } 843 844 /** 845 * Supports Parcelable 846 */ 847 public static final Parcelable.Creator<Account> CREATOR 848 = new Parcelable.Creator<Account>() { 849 @Override 850 public Account createFromParcel(Parcel in) { 851 return new Account(in); 852 } 853 854 @Override 855 public Account[] newArray(int size) { 856 return new Account[size]; 857 } 858 }; 859 860 /** 861 * Supports Parcelable 862 */ 863 @Override 864 public void writeToParcel(Parcel dest, int flags) { 865 // mBaseUri is not parceled 866 dest.writeLong(mId); 867 dest.writeString(mDisplayName); 868 dest.writeString(mEmailAddress); 869 dest.writeString(mSyncKey); 870 dest.writeInt(mSyncLookback); 871 dest.writeInt(mSyncInterval); 872 dest.writeLong(mHostAuthKeyRecv); 873 dest.writeLong(mHostAuthKeySend); 874 dest.writeInt(mFlags); 875 dest.writeByte(mIsDefault ? (byte)1 : (byte)0); 876 dest.writeString(mCompatibilityUuid); 877 dest.writeString(mSenderName); 878 dest.writeString(mRingtoneUri); 879 dest.writeString(mProtocolVersion); 880 dest.writeInt(mNewMessageCount); 881 dest.writeString(mSecuritySyncKey); 882 dest.writeString(mSignature); 883 dest.writeLong(mPolicyKey); 884 885 if (mHostAuthRecv != null) { 886 dest.writeByte((byte)1); 887 mHostAuthRecv.writeToParcel(dest, flags); 888 } else { 889 dest.writeByte((byte)0); 890 } 891 892 if (mHostAuthSend != null) { 893 dest.writeByte((byte)1); 894 mHostAuthSend.writeToParcel(dest, flags); 895 } else { 896 dest.writeByte((byte)0); 897 } 898 } 899 900 /** 901 * Supports Parcelable 902 */ 903 public Account(Parcel in) { 904 mBaseUri = Account.CONTENT_URI; 905 mId = in.readLong(); 906 mDisplayName = in.readString(); 907 mEmailAddress = in.readString(); 908 mSyncKey = in.readString(); 909 mSyncLookback = in.readInt(); 910 mSyncInterval = in.readInt(); 911 mHostAuthKeyRecv = in.readLong(); 912 mHostAuthKeySend = in.readLong(); 913 mFlags = in.readInt(); 914 mIsDefault = in.readByte() == 1; 915 mCompatibilityUuid = in.readString(); 916 mSenderName = in.readString(); 917 mRingtoneUri = in.readString(); 918 mProtocolVersion = in.readString(); 919 mNewMessageCount = in.readInt(); 920 mSecuritySyncKey = in.readString(); 921 mSignature = in.readString(); 922 mPolicyKey = in.readLong(); 923 924 mHostAuthRecv = null; 925 if (in.readByte() == 1) { 926 mHostAuthRecv = new HostAuth(in); 927 } 928 929 mHostAuthSend = null; 930 if (in.readByte() == 1) { 931 mHostAuthSend = new HostAuth(in); 932 } 933 } 934 935 /** 936 * For debugger support only - DO NOT use for code. 937 */ 938 @Override 939 public String toString() { 940 StringBuilder sb = new StringBuilder('['); 941 if (mHostAuthRecv != null && mHostAuthRecv.mProtocol != null) { 942 sb.append(mHostAuthRecv.mProtocol); 943 sb.append(':'); 944 } 945 if (mDisplayName != null) sb.append(mDisplayName); 946 sb.append(':'); 947 if (mEmailAddress != null) sb.append(mEmailAddress); 948 sb.append(':'); 949 if (mSenderName != null) sb.append(mSenderName); 950 sb.append(']'); 951 return sb.toString(); 952 } 953 954}