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