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