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