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