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.ContentObserver; 27 import android.database.Cursor; 28 import android.media.RingtoneManager; 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.utility.Utility; 35 import com.android.mail.utils.LogUtils; 36 import com.google.common.annotations.VisibleForTesting; 37 38 import org.json.JSONException; 39 import org.json.JSONObject; 40 41 import java.util.ArrayList; 42 43 public final class Account extends EmailContent implements Parcelable { 44 public static final String TABLE_NAME = "Account"; 45 46 // Define all pseudo account IDs here to avoid conflict with one another. 47 /** 48 * Pseudo account ID to represent a "combined account" that includes messages and mailboxes 49 * from all defined accounts. 50 * 51 * <em>IMPORTANT</em>: This must never be stored to the database. 52 */ 53 public static final long ACCOUNT_ID_COMBINED_VIEW = 0x1000000000000000L; 54 /** 55 * Pseudo account ID to represent "no account". This may be used any time the account ID 56 * may not be known or when we want to specifically select "no" account. 57 * 58 * <em>IMPORTANT</em>: This must never be stored to the database. 59 */ 60 public static final long NO_ACCOUNT = -1L; 61 62 /** 63 * Whether or not the user has asked for notifications of new mail in this account 64 * 65 * @deprecated Used only for migration 66 */ 67 @Deprecated 68 public final static int FLAGS_NOTIFY_NEW_MAIL = 1<<0; 69 /** 70 * Whether or not the user has asked for vibration notifications with all new mail 71 * 72 * @deprecated Used only for migration 73 */ 74 @Deprecated 75 public final static int FLAGS_VIBRATE = 1<<1; 76 // Bit mask for the account's deletion policy (see DELETE_POLICY_x below) 77 public static final int FLAGS_DELETE_POLICY_MASK = 1<<2 | 1<<3; 78 public static final int FLAGS_DELETE_POLICY_SHIFT = 2; 79 // Whether the account is in the process of being created; any account reconciliation code 80 // MUST ignore accounts with this bit set; in addition, ContentObservers for this data 81 // SHOULD consider the state of this flag during operation 82 public static final int FLAGS_INCOMPLETE = 1<<4; 83 // Security hold is used when the device is not in compliance with security policies 84 // required by the server; in this state, the user MUST be alerted to the need to update 85 // security settings. Sync adapters SHOULD NOT attempt to sync when this flag is set. 86 public static final int FLAGS_SECURITY_HOLD = 1<<5; 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 // Whether or not the initial folder list has been loaded 104 public static final int FLAGS_INITIAL_FOLDER_LIST_LOADED = 1<<13; 105 106 // Deletion policy (see FLAGS_DELETE_POLICY_MASK, above) 107 public static final int DELETE_POLICY_NEVER = 0; 108 public static final int DELETE_POLICY_7DAYS = 1<<0; // not supported 109 public static final int DELETE_POLICY_ON_DELETE = 1<<1; 110 111 // Sentinel values for the mSyncInterval field of both Account records 112 public static final int CHECK_INTERVAL_NEVER = -1; 113 public static final int CHECK_INTERVAL_PUSH = -2; 114 115 public static Uri CONTENT_URI; 116 public static Uri RESET_NEW_MESSAGE_COUNT_URI; 117 public static Uri NOTIFIER_URI; 118 119 public static void initAccount() { 120 CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/account"); 121 RESET_NEW_MESSAGE_COUNT_URI = Uri.parse(EmailContent.CONTENT_URI + "/resetNewMessageCount"); 122 NOTIFIER_URI = Uri.parse(EmailContent.CONTENT_NOTIFIER_URI + "/account"); 123 } 124 125 public String mDisplayName; 126 public String mEmailAddress; 127 public String mSyncKey; 128 public int mSyncLookback; 129 public int mSyncInterval; 130 public long mHostAuthKeyRecv; 131 public long mHostAuthKeySend; 132 public int mFlags; 133 public String mSenderName; 134 /** @deprecated Used only for migration */ 135 @Deprecated 136 private String mRingtoneUri; 137 public String mProtocolVersion; 138 public String mSecuritySyncKey; 139 public String mSignature; 140 public long mPolicyKey; 141 public long mPingDuration; 142 143 @VisibleForTesting 144 static final String JSON_TAG_HOST_AUTH_RECV = "hostAuthRecv"; 145 @VisibleForTesting 146 static final String JSON_TAG_HOST_AUTH_SEND = "hostAuthSend"; 147 148 // Convenience for creating/working with an account 149 public transient HostAuth mHostAuthRecv; 150 public transient HostAuth mHostAuthSend; 151 public transient Policy mPolicy; 152 153 // Marks this account as being a temporary entry, so we know to use it directly and not go 154 // through the database or any caches 155 private transient boolean mTemporary; 156 157 public static final int CONTENT_ID_COLUMN = 0; 158 public static final int CONTENT_DISPLAY_NAME_COLUMN = 1; 159 public static final int CONTENT_EMAIL_ADDRESS_COLUMN = 2; 160 public static final int CONTENT_SYNC_KEY_COLUMN = 3; 161 public static final int CONTENT_SYNC_LOOKBACK_COLUMN = 4; 162 public static final int CONTENT_SYNC_INTERVAL_COLUMN = 5; 163 public static final int CONTENT_HOST_AUTH_KEY_RECV_COLUMN = 6; 164 public static final int CONTENT_HOST_AUTH_KEY_SEND_COLUMN = 7; 165 public static final int CONTENT_FLAGS_COLUMN = 8; 166 public static final int CONTENT_SENDER_NAME_COLUMN = 9; 167 public static final int CONTENT_RINGTONE_URI_COLUMN = 10; 168 public static final int CONTENT_PROTOCOL_VERSION_COLUMN = 11; 169 public static final int CONTENT_SECURITY_SYNC_KEY_COLUMN = 12; 170 public static final int CONTENT_SIGNATURE_COLUMN = 13; 171 public static final int CONTENT_POLICY_KEY_COLUMN = 14; 172 public static final int CONTENT_PING_DURATION_COLUMN = 15; 173 public static final int CONTENT_MAX_ATTACHMENT_SIZE_COLUMN = 16; 174 175 public static final String[] CONTENT_PROJECTION = { 176 AttachmentColumns._ID, AccountColumns.DISPLAY_NAME, 177 AccountColumns.EMAIL_ADDRESS, AccountColumns.SYNC_KEY, AccountColumns.SYNC_LOOKBACK, 178 AccountColumns.SYNC_INTERVAL, AccountColumns.HOST_AUTH_KEY_RECV, 179 AccountColumns.HOST_AUTH_KEY_SEND, AccountColumns.FLAGS, 180 AccountColumns.SENDER_NAME, 181 AccountColumns.RINGTONE_URI, AccountColumns.PROTOCOL_VERSION, 182 AccountColumns.SECURITY_SYNC_KEY, 183 AccountColumns.SIGNATURE, AccountColumns.POLICY_KEY, AccountColumns.PING_DURATION, 184 AccountColumns.MAX_ATTACHMENT_SIZE 185 }; 186 187 public static final int ACCOUNT_FLAGS_COLUMN_ID = 0; 188 public static final int ACCOUNT_FLAGS_COLUMN_FLAGS = 1; 189 public static final String[] ACCOUNT_FLAGS_PROJECTION = { 190 AccountColumns._ID, AccountColumns.FLAGS}; 191 192 public static final String SECURITY_NONZERO_SELECTION = 193 AccountColumns.POLICY_KEY + " IS NOT NULL AND " + AccountColumns.POLICY_KEY + "!=0"; 194 195 private static final String FIND_INBOX_SELECTION = 196 MailboxColumns.TYPE + " = " + Mailbox.TYPE_INBOX + 197 " AND " + MailboxColumns.ACCOUNT_KEY + " =?"; 198 199 public Account() { 200 mBaseUri = CONTENT_URI; 201 202 // other defaults (policy) 203 mRingtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION).toString(); 204 mSyncInterval = -1; 205 mSyncLookback = -1; 206 mFlags = 0; 207 } 208 209 public static Account restoreAccountWithId(Context context, long id) { 210 return restoreAccountWithId(context, id, null); 211 } 212 213 public static Account restoreAccountWithId(Context context, long id, ContentObserver observer) { 214 return EmailContent.restoreContentWithId(context, Account.class, 215 Account.CONTENT_URI, Account.CONTENT_PROJECTION, id, observer); 216 } 217 218 public static Account restoreAccountWithAddress(Context context, String emailAddress) { 219 return restoreAccountWithAddress(context, emailAddress, null); 220 } 221 222 public static Account restoreAccountWithAddress(Context context, String emailAddress, 223 ContentObserver observer) { 224 final Cursor c = context.getContentResolver().query(CONTENT_URI, 225 new String[] {AccountColumns._ID}, 226 AccountColumns.EMAIL_ADDRESS + "=?", new String[] {emailAddress}, 227 null); 228 if (c == null || !c.moveToFirst()) { 229 return null; 230 } 231 final long id = c.getLong(c.getColumnIndex(AccountColumns._ID)); 232 return restoreAccountWithId(context, id, observer); 233 } 234 235 @Override 236 protected Uri getContentNotificationUri() { 237 return Account.CONTENT_URI; 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 mSenderName = cursor.getString(CONTENT_SENDER_NAME_COLUMN); 270 mRingtoneUri = cursor.getString(CONTENT_RINGTONE_URI_COLUMN); 271 mProtocolVersion = cursor.getString(CONTENT_PROTOCOL_VERSION_COLUMN); 272 mSecuritySyncKey = cursor.getString(CONTENT_SECURITY_SYNC_KEY_COLUMN); 273 mSignature = cursor.getString(CONTENT_SIGNATURE_COLUMN); 274 mPolicyKey = cursor.getLong(CONTENT_POLICY_KEY_COLUMN); 275 mPingDuration = cursor.getLong(CONTENT_PING_DURATION_COLUMN); 276 } 277 278 public boolean isTemporary() { 279 return mTemporary; 280 } 281 282 public void setTemporary(boolean temporary) { 283 mTemporary = temporary; 284 } 285 286 private static long getId(Uri u) { 287 return Long.parseLong(u.getPathSegments().get(1)); 288 } 289 290 public long getId() { 291 return mId; 292 } 293 294 /** 295 * Returns the user-visible name for the account, eg. "My work address" 296 * or "foo (at) exemple.com". 297 * @return the user-visible name for the account. 298 */ 299 public String getDisplayName() { 300 return mDisplayName; 301 } 302 303 /** 304 * Set the description. Be sure to call save() to commit to database. 305 * @param description the new description 306 */ 307 public void setDisplayName(String description) { 308 mDisplayName = description; 309 } 310 311 /** 312 * @return the email address for this account 313 */ 314 public String getEmailAddress() { 315 return mEmailAddress; 316 } 317 318 /** 319 * Set the Email address for this account. Be sure to call save() to commit to database. 320 * @param emailAddress the new email address for this account 321 */ 322 public void setEmailAddress(String emailAddress) { 323 mEmailAddress = emailAddress; 324 } 325 326 /** 327 * @return the sender's name for this account 328 */ 329 public String getSenderName() { 330 return mSenderName; 331 } 332 333 /** 334 * Set the sender's name. Be sure to call save() to commit to database. 335 * @param name the new sender name 336 */ 337 public void setSenderName(String name) { 338 mSenderName = name; 339 } 340 341 public String getSignature() { 342 return mSignature; 343 } 344 345 @VisibleForTesting 346 public void setSignature(String signature) { 347 mSignature = signature; 348 } 349 350 /** 351 * @return the minutes per check (for polling) 352 * TODO define sentinel values for "never", "push", etc. See Account.java 353 */ 354 public int getSyncInterval() { 355 return mSyncInterval; 356 } 357 358 /** 359 * Set the minutes per check (for polling). Be sure to call save() to commit to database. 360 * TODO define sentinel values for "never", "push", etc. See Account.java 361 * @param minutes the number of minutes between polling checks 362 */ 363 public void setSyncInterval(int minutes) { 364 mSyncInterval = minutes; 365 } 366 367 /** 368 * @return One of the {@code Account.SYNC_WINDOW_*} constants that represents the sync 369 * lookback window. 370 * TODO define sentinel values for "all", "1 month", etc. See Account.java 371 */ 372 public int getSyncLookback() { 373 return mSyncLookback; 374 } 375 376 /** 377 * Set the sync lookback window. Be sure to call save() to commit to database. 378 * TODO define sentinel values for "all", "1 month", etc. See Account.java 379 * @param value One of the {@link com.android.emailcommon.service.SyncWindow} constants 380 */ 381 public void setSyncLookback(int value) { 382 mSyncLookback = value; 383 } 384 385 /** 386 * @return the current ping duration. 387 */ 388 public long getPingDuration() { 389 return mPingDuration; 390 } 391 392 /** 393 * Set the ping duration. Be sure to call save() to commit to database. 394 */ 395 public void setPingDuration(long value) { 396 mPingDuration = value; 397 } 398 399 /** 400 * @return the flags for this account 401 */ 402 public int getFlags() { 403 return mFlags; 404 } 405 406 /** 407 * Set the flags for this account 408 * @param newFlags the new value for the flags 409 */ 410 public void setFlags(int newFlags) { 411 mFlags = newFlags; 412 } 413 414 /** 415 * @return the ringtone Uri for this account 416 * @deprecated Used only for migration 417 */ 418 @Deprecated 419 public String getRingtone() { 420 return mRingtoneUri; 421 } 422 423 /** 424 * Set the "delete policy" as a simple 0,1,2 value set. 425 * @param newPolicy the new delete policy 426 */ 427 public void setDeletePolicy(int newPolicy) { 428 mFlags &= ~FLAGS_DELETE_POLICY_MASK; 429 mFlags |= (newPolicy << FLAGS_DELETE_POLICY_SHIFT) & FLAGS_DELETE_POLICY_MASK; 430 } 431 432 /** 433 * Return the "delete policy" as a simple 0,1,2 value set. 434 * @return the current delete policy 435 */ 436 public int getDeletePolicy() { 437 return (mFlags & FLAGS_DELETE_POLICY_MASK) >> FLAGS_DELETE_POLICY_SHIFT; 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 * Return the id of the default account. If one hasn't been explicitly specified, return the 464 * first one in the database. If no account exists, returns {@link #NO_ACCOUNT}. 465 * 466 * @param context the caller's context 467 * @param lastUsedAccountId the last used account id, which is the basis of the default account 468 */ 469 public static long getDefaultAccountId(final Context context, final long lastUsedAccountId) { 470 final Cursor cursor = context.getContentResolver().query( 471 CONTENT_URI, ID_PROJECTION, null, null, null); 472 473 long firstAccount = NO_ACCOUNT; 474 475 try { 476 if (cursor != null && cursor.moveToFirst()) { 477 do { 478 final long accountId = cursor.getLong(Account.ID_PROJECTION_COLUMN); 479 480 if (accountId == lastUsedAccountId) { 481 return accountId; 482 } 483 484 if (firstAccount == NO_ACCOUNT) { 485 firstAccount = accountId; 486 } 487 } while (cursor.moveToNext()); 488 } 489 } finally { 490 if (cursor != null) { 491 cursor.close(); 492 } 493 } 494 495 return firstAccount; 496 } 497 498 /** 499 * Given an account id, return the account's protocol 500 * @param context the caller's context 501 * @param accountId the id of the account to be examined 502 * @return the account's protocol (or null if the Account or HostAuth do not exist) 503 */ 504 public static String getProtocol(Context context, long accountId) { 505 Account account = Account.restoreAccountWithId(context, accountId); 506 if (account != null) { 507 return account.getProtocol(context); 508 } 509 return null; 510 } 511 512 /** 513 * Return the account's protocol 514 * @param context the caller's context 515 * @return the account's protocol (or null if the HostAuth doesn't not exist) 516 */ 517 public String getProtocol(Context context) { 518 HostAuth hostAuth = getOrCreateHostAuthRecv(context); 519 if (hostAuth != null) { 520 return hostAuth.mProtocol; 521 } 522 return null; 523 } 524 525 /** 526 * Return a corresponding account manager object using the passed in type 527 * 528 * @param type We can't look up the account type from here, so pass it in 529 * @return system account object 530 */ 531 public android.accounts.Account getAccountManagerAccount(String type) { 532 return new android.accounts.Account(mEmailAddress, type); 533 } 534 535 /** 536 * Return the account ID for a message with a given id 537 * 538 * @param context the caller's context 539 * @param messageId the id of the message 540 * @return the account ID, or -1 if the account doesn't exist 541 */ 542 public static long getAccountIdForMessageId(Context context, long messageId) { 543 return Message.getKeyColumnLong(context, messageId, MessageColumns.ACCOUNT_KEY); 544 } 545 546 /** 547 * Return the account for a message with a given id 548 * @param context the caller's context 549 * @param messageId the id of the message 550 * @return the account, or null if the account doesn't exist 551 */ 552 public static Account getAccountForMessageId(Context context, long messageId) { 553 long accountId = getAccountIdForMessageId(context, messageId); 554 if (accountId != -1) { 555 return Account.restoreAccountWithId(context, accountId); 556 } 557 return null; 558 } 559 560 /** 561 * @return true if an {@code accountId} is assigned to any existing account. 562 */ 563 public static boolean isValidId(Context context, long accountId) { 564 return null != Utility.getFirstRowLong(context, CONTENT_URI, ID_PROJECTION, 565 ID_SELECTION, new String[] {Long.toString(accountId)}, null, 566 ID_PROJECTION_COLUMN); 567 } 568 569 /** 570 * Check a single account for security hold status. 571 */ 572 public static boolean isSecurityHold(Context context, long accountId) { 573 return (Utility.getFirstRowLong(context, 574 ContentUris.withAppendedId(Account.CONTENT_URI, accountId), 575 ACCOUNT_FLAGS_PROJECTION, null, null, null, ACCOUNT_FLAGS_COLUMN_FLAGS, 0L) 576 & Account.FLAGS_SECURITY_HOLD) != 0; 577 } 578 579 /** 580 * @return id of the "inbox" mailbox, or -1 if not found. 581 */ 582 public static long getInboxId(Context context, long accountId) { 583 return Utility.getFirstRowLong(context, Mailbox.CONTENT_URI, ID_PROJECTION, 584 FIND_INBOX_SELECTION, new String[] {Long.toString(accountId)}, null, 585 ID_PROJECTION_COLUMN, -1L); 586 } 587 588 /** 589 * Clear all account hold flags that are set. 590 * 591 * (This will trigger watchers, and in particular will cause EAS to try and resync the 592 * account(s).) 593 */ 594 public static void clearSecurityHoldOnAllAccounts(Context context) { 595 ContentResolver resolver = context.getContentResolver(); 596 Cursor c = resolver.query(Account.CONTENT_URI, ACCOUNT_FLAGS_PROJECTION, 597 SECURITY_NONZERO_SELECTION, null, null); 598 try { 599 while (c.moveToNext()) { 600 int flags = c.getInt(ACCOUNT_FLAGS_COLUMN_FLAGS); 601 602 if (0 != (flags & FLAGS_SECURITY_HOLD)) { 603 ContentValues cv = new ContentValues(); 604 cv.put(AccountColumns.FLAGS, flags & ~FLAGS_SECURITY_HOLD); 605 long accountId = c.getLong(ACCOUNT_FLAGS_COLUMN_ID); 606 Uri uri = ContentUris.withAppendedId(Account.CONTENT_URI, accountId); 607 resolver.update(uri, cv, null, null); 608 } 609 } 610 } finally { 611 c.close(); 612 } 613 } 614 615 /* 616 * Override this so that we can store the HostAuth's first and link them to the Account 617 * (non-Javadoc) 618 * @see com.android.email.provider.EmailContent#save(android.content.Context) 619 */ 620 @Override 621 public Uri save(Context context) { 622 if (isSaved()) { 623 throw new UnsupportedOperationException(); 624 } 625 // This logic is in place so I can (a) short circuit the expensive stuff when 626 // possible, and (b) override (and throw) if anyone tries to call save() or update() 627 // directly for Account, which are unsupported. 628 if (mHostAuthRecv == null && mHostAuthSend == null && mPolicy != null) { 629 return super.save(context); 630 } 631 632 int index = 0; 633 int recvIndex = -1; 634 int recvCredentialsIndex = -1; 635 int sendIndex = -1; 636 int sendCredentialsIndex = -1; 637 638 // Create operations for saving the send and recv hostAuths, and their credentials. 639 // Also, remember which operation in the array they represent 640 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); 641 if (mHostAuthRecv != null) { 642 if (mHostAuthRecv.mCredential != null) { 643 recvCredentialsIndex = index++; 644 ops.add(ContentProviderOperation.newInsert(mHostAuthRecv.mCredential.mBaseUri) 645 .withValues(mHostAuthRecv.mCredential.toContentValues()) 646 .build()); 647 } 648 recvIndex = index++; 649 final ContentProviderOperation.Builder b = ContentProviderOperation.newInsert( 650 mHostAuthRecv.mBaseUri); 651 b.withValues(mHostAuthRecv.toContentValues()); 652 if (recvCredentialsIndex >= 0) { 653 final ContentValues cv = new ContentValues(); 654 cv.put(HostAuthColumns.CREDENTIAL_KEY, recvCredentialsIndex); 655 b.withValueBackReferences(cv); 656 } 657 ops.add(b.build()); 658 } 659 if (mHostAuthSend != null) { 660 if (mHostAuthSend.mCredential != null) { 661 if (mHostAuthRecv.mCredential != null && 662 mHostAuthRecv.mCredential.equals(mHostAuthSend.mCredential)) { 663 // These two credentials are identical, use the same row. 664 sendCredentialsIndex = recvCredentialsIndex; 665 } else { 666 sendCredentialsIndex = index++; 667 ops.add(ContentProviderOperation.newInsert(mHostAuthSend.mCredential.mBaseUri) 668 .withValues(mHostAuthSend.mCredential.toContentValues()) 669 .build()); 670 } 671 } 672 sendIndex = index++; 673 final ContentProviderOperation.Builder b = ContentProviderOperation.newInsert( 674 mHostAuthSend.mBaseUri); 675 b.withValues(mHostAuthSend.toContentValues()); 676 if (sendCredentialsIndex >= 0) { 677 final ContentValues cv = new ContentValues(); 678 cv.put(HostAuthColumns.CREDENTIAL_KEY, sendCredentialsIndex); 679 b.withValueBackReferences(cv); 680 } 681 ops.add(b.build()); 682 } 683 684 // Now do the Account 685 ContentValues cv = null; 686 if (recvIndex >= 0 || sendIndex >= 0) { 687 cv = new ContentValues(); 688 if (recvIndex >= 0) { 689 cv.put(AccountColumns.HOST_AUTH_KEY_RECV, recvIndex); 690 } 691 if (sendIndex >= 0) { 692 cv.put(AccountColumns.HOST_AUTH_KEY_SEND, sendIndex); 693 } 694 } 695 696 ContentProviderOperation.Builder b = ContentProviderOperation.newInsert(mBaseUri); 697 b.withValues(toContentValues()); 698 if (cv != null) { 699 b.withValueBackReferences(cv); 700 } 701 ops.add(b.build()); 702 703 try { 704 ContentProviderResult[] results = 705 context.getContentResolver().applyBatch(EmailContent.AUTHORITY, ops); 706 // If saving, set the mId's of the various saved objects 707 if (recvIndex >= 0) { 708 long newId = getId(results[recvIndex].uri); 709 mHostAuthKeyRecv = newId; 710 mHostAuthRecv.mId = newId; 711 } 712 if (sendIndex >= 0) { 713 long newId = getId(results[sendIndex].uri); 714 mHostAuthKeySend = newId; 715 mHostAuthSend.mId = newId; 716 } 717 Uri u = results[index].uri; 718 mId = getId(u); 719 return u; 720 } catch (RemoteException e) { 721 // There is nothing to be done here; fail by returning null 722 } catch (OperationApplicationException e) { 723 // There is nothing to be done here; fail by returning null 724 } 725 return null; 726 } 727 728 @Override 729 public ContentValues toContentValues() { 730 ContentValues values = new ContentValues(); 731 values.put(AccountColumns.DISPLAY_NAME, mDisplayName); 732 values.put(AccountColumns.EMAIL_ADDRESS, mEmailAddress); 733 values.put(AccountColumns.SYNC_KEY, mSyncKey); 734 values.put(AccountColumns.SYNC_LOOKBACK, mSyncLookback); 735 values.put(AccountColumns.SYNC_INTERVAL, mSyncInterval); 736 values.put(AccountColumns.HOST_AUTH_KEY_RECV, mHostAuthKeyRecv); 737 values.put(AccountColumns.HOST_AUTH_KEY_SEND, mHostAuthKeySend); 738 values.put(AccountColumns.FLAGS, mFlags); 739 values.put(AccountColumns.SENDER_NAME, mSenderName); 740 values.put(AccountColumns.RINGTONE_URI, mRingtoneUri); 741 values.put(AccountColumns.PROTOCOL_VERSION, mProtocolVersion); 742 values.put(AccountColumns.SECURITY_SYNC_KEY, mSecuritySyncKey); 743 values.put(AccountColumns.SIGNATURE, mSignature); 744 values.put(AccountColumns.POLICY_KEY, mPolicyKey); 745 values.put(AccountColumns.PING_DURATION, mPingDuration); 746 return values; 747 } 748 749 public String toJsonString(final Context context) { 750 ensureLoaded(context); 751 final JSONObject json = toJson(); 752 if (json != null) { 753 return json.toString(); 754 } 755 return null; 756 } 757 758 protected JSONObject toJson() { 759 try { 760 final JSONObject json = new JSONObject(); 761 json.putOpt(AccountColumns.DISPLAY_NAME, mDisplayName); 762 json.put(AccountColumns.EMAIL_ADDRESS, mEmailAddress); 763 json.put(AccountColumns.SYNC_LOOKBACK, mSyncLookback); 764 json.put(AccountColumns.SYNC_INTERVAL, mSyncInterval); 765 final JSONObject recvJson = mHostAuthRecv.toJson(); 766 json.put(JSON_TAG_HOST_AUTH_RECV, recvJson); 767 if (mHostAuthSend != null) { 768 final JSONObject sendJson = mHostAuthSend.toJson(); 769 json.put(JSON_TAG_HOST_AUTH_SEND, sendJson); 770 } 771 json.put(AccountColumns.FLAGS, mFlags); 772 json.putOpt(AccountColumns.SENDER_NAME, mSenderName); 773 json.putOpt(AccountColumns.PROTOCOL_VERSION, mProtocolVersion); 774 json.putOpt(AccountColumns.SIGNATURE, mSignature); 775 json.put(AccountColumns.PING_DURATION, mPingDuration); 776 return json; 777 } catch (final JSONException e) { 778 LogUtils.d(LogUtils.TAG, e, "Exception while serializing Account"); 779 } 780 return null; 781 } 782 783 public static Account fromJsonString(final String jsonString) { 784 try { 785 final JSONObject json = new JSONObject(jsonString); 786 return fromJson(json); 787 } catch (final JSONException e) { 788 LogUtils.d(LogUtils.TAG, e, "Could not parse json for account"); 789 } 790 return null; 791 } 792 793 protected static Account fromJson(final JSONObject json) { 794 try { 795 final Account a = new Account(); 796 a.mDisplayName = json.optString(AccountColumns.DISPLAY_NAME); 797 a.mEmailAddress = json.getString(AccountColumns.EMAIL_ADDRESS); 798 // SYNC_KEY is not stored 799 a.mSyncLookback = json.getInt(AccountColumns.SYNC_LOOKBACK); 800 a.mSyncInterval = json.getInt(AccountColumns.SYNC_INTERVAL); 801 final JSONObject recvJson = json.getJSONObject(JSON_TAG_HOST_AUTH_RECV); 802 a.mHostAuthRecv = HostAuth.fromJson(recvJson); 803 final JSONObject sendJson = json.optJSONObject(JSON_TAG_HOST_AUTH_SEND); 804 if (sendJson != null) { 805 a.mHostAuthSend = HostAuth.fromJson(sendJson); 806 } 807 a.mFlags = json.getInt(AccountColumns.FLAGS); 808 a.mSenderName = json.optString(AccountColumns.SENDER_NAME); 809 a.mProtocolVersion = json.optString(AccountColumns.PROTOCOL_VERSION); 810 // SECURITY_SYNC_KEY is not stored 811 a.mSignature = json.optString(AccountColumns.SIGNATURE); 812 // POLICY_KEY is not stored 813 a.mPingDuration = json.optInt(AccountColumns.PING_DURATION, 0); 814 return a; 815 } catch (final JSONException e) { 816 LogUtils.d(LogUtils.TAG, e, "Exception while deserializing Account"); 817 } 818 return null; 819 } 820 821 /** 822 * Ensure that all optionally-loaded fields are populated from the provider. 823 * @param context for provider loads 824 */ 825 public void ensureLoaded(final Context context) { 826 if (mHostAuthKeyRecv == 0 && mHostAuthRecv == null) { 827 throw new IllegalStateException("Trying to load incomplete Account object"); 828 } 829 getOrCreateHostAuthRecv(context).ensureLoaded(context); 830 831 if (mHostAuthKeySend != 0) { 832 getOrCreateHostAuthSend(context); 833 if (mHostAuthSend != null) { 834 mHostAuthSend.ensureLoaded(context); 835 } 836 } 837 } 838 839 /** 840 * Supports Parcelable 841 */ 842 @Override 843 public int describeContents() { 844 return 0; 845 } 846 847 /** 848 * Supports Parcelable 849 */ 850 public static final Parcelable.Creator<Account> CREATOR 851 = new Parcelable.Creator<Account>() { 852 @Override 853 public Account createFromParcel(Parcel in) { 854 return new Account(in); 855 } 856 857 @Override 858 public Account[] newArray(int size) { 859 return new Account[size]; 860 } 861 }; 862 863 /** 864 * Supports Parcelable 865 */ 866 @Override 867 public void writeToParcel(Parcel dest, int flags) { 868 // mBaseUri is not parceled 869 dest.writeLong(mId); 870 dest.writeString(mDisplayName); 871 dest.writeString(mEmailAddress); 872 dest.writeString(mSyncKey); 873 dest.writeInt(mSyncLookback); 874 dest.writeInt(mSyncInterval); 875 dest.writeLong(mHostAuthKeyRecv); 876 dest.writeLong(mHostAuthKeySend); 877 dest.writeInt(mFlags); 878 dest.writeString("" /* mCompatibilityUuid */); 879 dest.writeString(mSenderName); 880 dest.writeString(mRingtoneUri); 881 dest.writeString(mProtocolVersion); 882 dest.writeInt(0 /* mNewMessageCount */); 883 dest.writeString(mSecuritySyncKey); 884 dest.writeString(mSignature); 885 dest.writeLong(mPolicyKey); 886 887 if (mHostAuthRecv != null) { 888 dest.writeByte((byte)1); 889 mHostAuthRecv.writeToParcel(dest, flags); 890 } else { 891 dest.writeByte((byte)0); 892 } 893 894 if (mHostAuthSend != null) { 895 dest.writeByte((byte)1); 896 mHostAuthSend.writeToParcel(dest, flags); 897 } else { 898 dest.writeByte((byte)0); 899 } 900 } 901 902 /** 903 * Supports Parcelable 904 */ 905 public Account(Parcel in) { 906 mBaseUri = Account.CONTENT_URI; 907 mId = in.readLong(); 908 mDisplayName = in.readString(); 909 mEmailAddress = in.readString(); 910 mSyncKey = in.readString(); 911 mSyncLookback = in.readInt(); 912 mSyncInterval = in.readInt(); 913 mHostAuthKeyRecv = in.readLong(); 914 mHostAuthKeySend = in.readLong(); 915 mFlags = in.readInt(); 916 /* mCompatibilityUuid = */ in.readString(); 917 mSenderName = in.readString(); 918 mRingtoneUri = in.readString(); 919 mProtocolVersion = in.readString(); 920 /* mNewMessageCount = */ in.readInt(); 921 mSecuritySyncKey = in.readString(); 922 mSignature = in.readString(); 923 mPolicyKey = in.readLong(); 924 925 mHostAuthRecv = null; 926 if (in.readByte() == 1) { 927 mHostAuthRecv = new HostAuth(in); 928 } 929 930 mHostAuthSend = null; 931 if (in.readByte() == 1) { 932 mHostAuthSend = new HostAuth(in); 933 } 934 } 935 936 /** 937 * For debugger support only - DO NOT use for code. 938 */ 939 @Override 940 public String toString() { 941 StringBuilder sb = new StringBuilder("["); 942 if (mHostAuthRecv != null && mHostAuthRecv.mProtocol != null) { 943 sb.append(mHostAuthRecv.mProtocol); 944 sb.append(':'); 945 } 946 if (mDisplayName != null) sb.append(mDisplayName); 947 sb.append(':'); 948 if (mEmailAddress != null) sb.append(mEmailAddress); 949 sb.append(':'); 950 if (mSenderName != null) sb.append(mSenderName); 951 sb.append(']'); 952 return sb.toString(); 953 } 954 } 955