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