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