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