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