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