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