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