1 /** 2 * Copyright (c) 2012, Google Inc. 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.mail.providers; 18 19 import android.content.ContentResolver; 20 import android.content.ContentValues; 21 import android.database.Cursor; 22 import android.database.MatrixCursor; 23 import android.net.Uri; 24 import android.os.Parcel; 25 import android.os.Parcelable; 26 import android.text.TextUtils; 27 28 import com.android.mail.content.CursorCreator; 29 import com.android.mail.content.ObjectCursor; 30 import com.android.mail.providers.UIProvider.AccountCapabilities; 31 import com.android.mail.providers.UIProvider.AccountColumns; 32 import com.android.mail.providers.UIProvider.SyncStatus; 33 import com.android.mail.utils.LogTag; 34 import com.android.mail.utils.LogUtils; 35 import com.android.mail.utils.Utils; 36 import com.google.common.base.Objects; 37 import com.google.common.collect.Lists; 38 39 import org.json.JSONArray; 40 import org.json.JSONException; 41 import org.json.JSONObject; 42 43 import java.util.HashMap; 44 import java.util.List; 45 import java.util.Map; 46 47 public class Account implements Parcelable { 48 private static final String SETTINGS_KEY = "settings"; 49 50 /** 51 * Human readable account name. Not guaranteed to be the account's email address, nor to match 52 * the system account manager. 53 */ 54 // TODO: Make this private and add getDisplayName() accessor 55 public final String name; 56 57 /** 58 * The real name associated with the account, e.g. "John Doe" 59 */ 60 private final String senderName; 61 62 /** 63 * Account manager name. MUST MATCH SYSTEM ACCOUNT MANAGER NAME 64 */ 65 66 private final String accountManagerName; 67 68 /** 69 * Account type. MUST MATCH SYSTEM ACCOUNT MANAGER TYPE 70 */ 71 72 private final String type; 73 74 /** 75 * Cached android.accounts.Account based on the above two values 76 */ 77 78 private android.accounts.Account amAccount; 79 80 /** 81 * The version of the UI provider schema from which this account provider 82 * will return results. 83 */ 84 public final int providerVersion; 85 86 /** 87 * The uri to directly access the information for this account. 88 */ 89 public final Uri uri; 90 91 /** 92 * The possible capabilities that this account supports. 93 */ 94 public final int capabilities; 95 96 /** 97 * The content provider uri to return the list of top level folders for this 98 * account. 99 */ 100 public final Uri folderListUri; 101 /** 102 * The content provider uri to return the list of all real folders for this 103 * account. 104 */ 105 public Uri fullFolderListUri; 106 /** 107 * The content provider uri to return the list of all real and synthetic folders for this 108 * account. 109 */ 110 public Uri allFolderListUri; 111 /** 112 * The content provider uri that can be queried for search results. 113 */ 114 public final Uri searchUri; 115 116 /** 117 * The custom from addresses for this account or null if there are none. 118 */ 119 public String accountFromAddresses; 120 121 /** 122 * The content provider uri that can be used to expunge message from this 123 * account. NOTE: This might be better to be an update operation on the 124 * messageUri. 125 */ 126 public final Uri expungeMessageUri; 127 128 /** 129 * The content provider uri that can be used to undo the last operation 130 * performed. 131 */ 132 public final Uri undoUri; 133 134 /** 135 * Uri for EDIT intent that will cause the settings screens for this account type to be 136 * shown. 137 */ 138 public final Uri settingsIntentUri; 139 140 /** 141 * Uri for VIEW intent that will cause the help screens for this account type to be 142 * shown. 143 */ 144 public final Uri helpIntentUri; 145 146 /** 147 * Uri for VIEW intent that will cause the send feedback screens for this account type to be 148 * shown. 149 */ 150 public final Uri sendFeedbackIntentUri; 151 152 /** 153 * Uri for VIEW intent that will cause the reauthentication screen for this account to be 154 * shown. 155 */ 156 public final Uri reauthenticationIntentUri; 157 158 /** 159 * The sync status of the account 160 */ 161 public final int syncStatus; 162 163 /** 164 * Uri for VIEW intent that will cause the compose screen for this account type to be 165 * shown. 166 */ 167 public final Uri composeIntentUri; 168 169 public final String mimeType; 170 /** 171 * URI for recent folders for this account. 172 */ 173 public final Uri recentFolderListUri; 174 /** 175 * The color used for this account in combined view (Email) 176 */ 177 public final int color; 178 /** 179 * URI for default recent folders for this account, if any. 180 */ 181 public final Uri defaultRecentFolderListUri; 182 /** 183 * Settings object for this account. 184 */ 185 public final Settings settings; 186 187 /** 188 * URI for forcing a manual sync of this account. 189 */ 190 public final Uri manualSyncUri; 191 192 /** 193 * URI for account type specific supplementary account info on outgoing links, if any. 194 */ 195 public final Uri viewIntentProxyUri; 196 197 /** 198 * URI for querying for the account cookies to be used when displaying inline content in a 199 * conversation 200 */ 201 public final Uri accoutCookieQueryUri; 202 203 /** 204 * URI to be used with an update() ContentResolver call with a {@link ContentValues} object 205 * where the keys are from the {@link AccountColumns.SettingsColumns}, and the values are the 206 * new values. 207 */ 208 public final Uri updateSettingsUri; 209 210 /** 211 * Whether message transforms (HTML DOM manipulation) feature is enabled. 212 */ 213 public final int enableMessageTransforms; 214 215 /** 216 * Sync authority used by the mail app. This can be used in 217 * {@link ContentResolver#getSyncAutomatically} calls to check for whether sync is enabled 218 * for this account and mail app. 219 */ 220 public final String syncAuthority; 221 222 public final Uri quickResponseUri; 223 224 /** 225 * Transient cache of parsed {@link #accountFromAddresses}, plus an entry for the main account 226 * address. 227 */ 228 private transient List<ReplyFromAccount> mReplyFroms; 229 230 private static final String LOG_TAG = LogTag.getLogTag(); 231 232 /** 233 * Return a serialized String for this account. 234 */ 235 public synchronized String serialize() { 236 JSONObject json = new JSONObject(); 237 try { 238 json.put(AccountColumns.NAME, name); 239 json.put(AccountColumns.TYPE, type); 240 json.put(AccountColumns.SENDER_NAME, senderName); 241 json.put(AccountColumns.ACCOUNT_MANAGER_NAME, accountManagerName); 242 json.put(AccountColumns.PROVIDER_VERSION, providerVersion); 243 json.put(AccountColumns.URI, uri); 244 json.put(AccountColumns.CAPABILITIES, capabilities); 245 json.put(AccountColumns.FOLDER_LIST_URI, folderListUri); 246 json.put(AccountColumns.FULL_FOLDER_LIST_URI, fullFolderListUri); 247 json.put(AccountColumns.ALL_FOLDER_LIST_URI, allFolderListUri); 248 json.put(AccountColumns.SEARCH_URI, searchUri); 249 json.put(AccountColumns.ACCOUNT_FROM_ADDRESSES, accountFromAddresses); 250 json.put(AccountColumns.EXPUNGE_MESSAGE_URI, expungeMessageUri); 251 json.put(AccountColumns.UNDO_URI, undoUri); 252 json.put(AccountColumns.SETTINGS_INTENT_URI, settingsIntentUri); 253 json.put(AccountColumns.HELP_INTENT_URI, helpIntentUri); 254 json.put(AccountColumns.SEND_FEEDBACK_INTENT_URI, sendFeedbackIntentUri); 255 json.put(AccountColumns.REAUTHENTICATION_INTENT_URI, reauthenticationIntentUri); 256 json.put(AccountColumns.SYNC_STATUS, syncStatus); 257 json.put(AccountColumns.COMPOSE_URI, composeIntentUri); 258 json.put(AccountColumns.MIME_TYPE, mimeType); 259 json.put(AccountColumns.RECENT_FOLDER_LIST_URI, recentFolderListUri); 260 json.put(AccountColumns.COLOR, color); 261 json.put(AccountColumns.DEFAULT_RECENT_FOLDER_LIST_URI, defaultRecentFolderListUri); 262 json.put(AccountColumns.MANUAL_SYNC_URI, manualSyncUri); 263 json.put(AccountColumns.VIEW_INTENT_PROXY_URI, viewIntentProxyUri); 264 json.put(AccountColumns.ACCOUNT_COOKIE_QUERY_URI, accoutCookieQueryUri); 265 json.put(AccountColumns.UPDATE_SETTINGS_URI, updateSettingsUri); 266 json.put(AccountColumns.ENABLE_MESSAGE_TRANSFORMS, enableMessageTransforms); 267 json.put(AccountColumns.SYNC_AUTHORITY, syncAuthority); 268 json.put(AccountColumns.QUICK_RESPONSE_URI, quickResponseUri); 269 if (settings != null) { 270 json.put(SETTINGS_KEY, settings.toJSON()); 271 } 272 } catch (JSONException e) { 273 LogUtils.wtf(LOG_TAG, e, "Could not serialize account with name %s", name); 274 } 275 return json.toString(); 276 } 277 278 /** 279 * Create a new instance of an Account object using a serialized instance created previously 280 * using {@link #serialize()}. This returns null if the serialized instance was invalid or does 281 * not represent a valid account object. 282 * 283 * @param serializedAccount JSON encoded account object 284 * @return Account object 285 */ 286 public static Account newinstance(String serializedAccount) { 287 // The heavy lifting is done by Account(name, type, serializedAccount). This method 288 // is a wrapper to check for errors and exceptions and return back a null in cases 289 // something breaks. 290 JSONObject json; 291 try { 292 json = new JSONObject(serializedAccount); 293 final String name = (String) json.get(UIProvider.AccountColumns.NAME); 294 final String type = (String) json.get(UIProvider.AccountColumns.TYPE); 295 return new Account(name, type, serializedAccount); 296 } catch (JSONException e) { 297 LogUtils.w(LOG_TAG, e, "Could not create an account from this input: \"%s\"", 298 serializedAccount); 299 return null; 300 } 301 } 302 303 /** 304 * Construct a new Account instance from a previously serialized string. This calls 305 * {@link android.accounts.Account#Account(String, String)} with name and type given as the 306 * first two arguments. 307 * 308 * <p> 309 * This is private. Public uses should go through the safe {@link #newinstance(String)} method. 310 * </p> 311 * @param acctName name of account in {@link android.accounts.Account} 312 * @param acctType type of account in {@link android.accounts.Account} 313 * @param jsonAccount string obtained from {@link #serialize()} on a valid account. 314 * @throws JSONException 315 */ 316 private Account(String acctName, String acctType, String jsonAccount) throws JSONException { 317 name = acctName; 318 type = acctType; 319 final JSONObject json = new JSONObject(jsonAccount); 320 senderName = json.optString(AccountColumns.SENDER_NAME); 321 final String amName = json.optString(AccountColumns.ACCOUNT_MANAGER_NAME); 322 // We need accountManagerName to be filled in, but we might be dealing with an old cache 323 // entry which doesn't have it, so use the display name instead in that case as a fallback 324 if (TextUtils.isEmpty(amName)) { 325 accountManagerName = name; 326 } else { 327 accountManagerName = amName; 328 } 329 providerVersion = json.getInt(AccountColumns.PROVIDER_VERSION); 330 uri = Uri.parse(json.optString(AccountColumns.URI)); 331 capabilities = json.getInt(AccountColumns.CAPABILITIES); 332 folderListUri = Utils 333 .getValidUri(json.optString(AccountColumns.FOLDER_LIST_URI)); 334 fullFolderListUri = Utils.getValidUri(json 335 .optString(AccountColumns.FULL_FOLDER_LIST_URI)); 336 allFolderListUri = Utils.getValidUri(json 337 .optString(AccountColumns.ALL_FOLDER_LIST_URI)); 338 searchUri = Utils.getValidUri(json.optString(AccountColumns.SEARCH_URI)); 339 accountFromAddresses = json.optString(AccountColumns.ACCOUNT_FROM_ADDRESSES, 340 ""); 341 expungeMessageUri = Utils.getValidUri(json 342 .optString(AccountColumns.EXPUNGE_MESSAGE_URI)); 343 undoUri = Utils.getValidUri(json.optString(AccountColumns.UNDO_URI)); 344 settingsIntentUri = Utils.getValidUri(json 345 .optString(AccountColumns.SETTINGS_INTENT_URI)); 346 helpIntentUri = Utils.getValidUri(json.optString(AccountColumns.HELP_INTENT_URI)); 347 sendFeedbackIntentUri = Utils.getValidUri(json 348 .optString(AccountColumns.SEND_FEEDBACK_INTENT_URI)); 349 reauthenticationIntentUri = Utils.getValidUri( 350 json.optString(AccountColumns.REAUTHENTICATION_INTENT_URI)); 351 syncStatus = json.optInt(AccountColumns.SYNC_STATUS); 352 composeIntentUri = Utils.getValidUri(json.optString(AccountColumns.COMPOSE_URI)); 353 mimeType = json.optString(AccountColumns.MIME_TYPE); 354 recentFolderListUri = Utils.getValidUri(json 355 .optString(AccountColumns.RECENT_FOLDER_LIST_URI)); 356 color = json.optInt(AccountColumns.COLOR, 0); 357 defaultRecentFolderListUri = Utils.getValidUri(json 358 .optString(AccountColumns.DEFAULT_RECENT_FOLDER_LIST_URI)); 359 manualSyncUri = Utils 360 .getValidUri(json.optString(AccountColumns.MANUAL_SYNC_URI)); 361 viewIntentProxyUri = Utils 362 .getValidUri(json.optString(AccountColumns.VIEW_INTENT_PROXY_URI)); 363 accoutCookieQueryUri = Utils.getValidUri( 364 json.optString(AccountColumns.ACCOUNT_COOKIE_QUERY_URI)); 365 updateSettingsUri = Utils.getValidUri( 366 json.optString(AccountColumns.UPDATE_SETTINGS_URI)); 367 enableMessageTransforms = json.optInt(AccountColumns.ENABLE_MESSAGE_TRANSFORMS); 368 syncAuthority = json.optString(AccountColumns.SYNC_AUTHORITY); 369 quickResponseUri = Utils.getValidUri(json.optString(AccountColumns.QUICK_RESPONSE_URI)); 370 371 final Settings jsonSettings = Settings.newInstance(json.optJSONObject(SETTINGS_KEY)); 372 if (jsonSettings != null) { 373 settings = jsonSettings; 374 } else { 375 LogUtils.e(LOG_TAG, new Throwable(), 376 "Unexpected null settings in Account(name, type, jsonAccount)"); 377 settings = Settings.EMPTY_SETTINGS; 378 } 379 } 380 381 public Account(Cursor cursor) { 382 name = cursor.getString(cursor.getColumnIndex(UIProvider.AccountColumns.NAME)); 383 senderName = cursor.getString(cursor.getColumnIndex(UIProvider.AccountColumns.SENDER_NAME)); 384 type = cursor.getString(cursor.getColumnIndex(UIProvider.AccountColumns.TYPE)); 385 accountManagerName = cursor.getString( 386 cursor.getColumnIndex(UIProvider.AccountColumns.ACCOUNT_MANAGER_NAME)); 387 accountFromAddresses = cursor.getString( 388 cursor.getColumnIndex(UIProvider.AccountColumns.ACCOUNT_FROM_ADDRESSES)); 389 390 final int capabilitiesColumnIndex = 391 cursor.getColumnIndex(UIProvider.AccountColumns.CAPABILITIES); 392 if (capabilitiesColumnIndex != -1) { 393 capabilities = cursor.getInt(capabilitiesColumnIndex); 394 } else { 395 capabilities = 0; 396 } 397 398 providerVersion = 399 cursor.getInt(cursor.getColumnIndex(UIProvider.AccountColumns.PROVIDER_VERSION)); 400 uri = Uri.parse(cursor.getString(cursor.getColumnIndex(UIProvider.AccountColumns.URI))); 401 folderListUri = Uri.parse( 402 cursor.getString(cursor.getColumnIndex(UIProvider.AccountColumns.FOLDER_LIST_URI))); 403 fullFolderListUri = Utils.getValidUri(cursor.getString( 404 cursor.getColumnIndex(UIProvider.AccountColumns.FULL_FOLDER_LIST_URI))); 405 allFolderListUri = Utils.getValidUri(cursor.getString( 406 cursor.getColumnIndex(UIProvider.AccountColumns.ALL_FOLDER_LIST_URI))); 407 searchUri = Utils.getValidUri( 408 cursor.getString(cursor.getColumnIndex(UIProvider.AccountColumns.SEARCH_URI))); 409 expungeMessageUri = Utils.getValidUri(cursor.getString( 410 cursor.getColumnIndex(UIProvider.AccountColumns.EXPUNGE_MESSAGE_URI))); 411 undoUri = Utils.getValidUri( 412 cursor.getString(cursor.getColumnIndex(UIProvider.AccountColumns.UNDO_URI))); 413 settingsIntentUri = Utils.getValidUri(cursor.getString( 414 cursor.getColumnIndex(UIProvider.AccountColumns.SETTINGS_INTENT_URI))); 415 helpIntentUri = Utils.getValidUri( 416 cursor.getString(cursor.getColumnIndex(UIProvider.AccountColumns.HELP_INTENT_URI))); 417 sendFeedbackIntentUri = Utils.getValidUri(cursor.getString( 418 cursor.getColumnIndex(UIProvider.AccountColumns.SEND_FEEDBACK_INTENT_URI))); 419 reauthenticationIntentUri = Utils.getValidUri(cursor.getString( 420 cursor.getColumnIndex(UIProvider.AccountColumns.REAUTHENTICATION_INTENT_URI))); 421 syncStatus = cursor.getInt(cursor.getColumnIndex(UIProvider.AccountColumns.SYNC_STATUS)); 422 composeIntentUri = Utils.getValidUri( 423 cursor.getString(cursor.getColumnIndex(UIProvider.AccountColumns.COMPOSE_URI))); 424 mimeType = cursor.getString(cursor.getColumnIndex(UIProvider.AccountColumns.MIME_TYPE)); 425 recentFolderListUri = Utils.getValidUri(cursor.getString( 426 cursor.getColumnIndex(UIProvider.AccountColumns.RECENT_FOLDER_LIST_URI))); 427 color = cursor.getInt(cursor.getColumnIndex(UIProvider.AccountColumns.COLOR)); 428 defaultRecentFolderListUri = Utils.getValidUri(cursor.getString( 429 cursor.getColumnIndex(UIProvider.AccountColumns.DEFAULT_RECENT_FOLDER_LIST_URI))); 430 manualSyncUri = Utils.getValidUri( 431 cursor.getString(cursor.getColumnIndex(UIProvider.AccountColumns.MANUAL_SYNC_URI))); 432 viewIntentProxyUri = Utils.getValidUri(cursor.getString( 433 cursor.getColumnIndex(UIProvider.AccountColumns.VIEW_INTENT_PROXY_URI))); 434 accoutCookieQueryUri = Utils.getValidUri(cursor.getString( 435 cursor.getColumnIndex(UIProvider.AccountColumns.ACCOUNT_COOKIE_QUERY_URI))); 436 updateSettingsUri = Utils.getValidUri(cursor.getString( 437 cursor.getColumnIndex(UIProvider.AccountColumns.UPDATE_SETTINGS_URI))); 438 enableMessageTransforms = cursor.getInt( 439 cursor.getColumnIndex(AccountColumns.ENABLE_MESSAGE_TRANSFORMS)); 440 syncAuthority = cursor.getString( 441 cursor.getColumnIndex(AccountColumns.SYNC_AUTHORITY)); 442 if (TextUtils.isEmpty(syncAuthority)) { 443 LogUtils.e(LOG_TAG, "Unexpected empty syncAuthority from cursor"); 444 } 445 quickResponseUri = Utils.getValidUri(cursor.getString( 446 cursor.getColumnIndex(AccountColumns.QUICK_RESPONSE_URI))); 447 settings = new Settings(cursor); 448 } 449 450 /** 451 * Returns an array of all Accounts located at this cursor. This method returns a zero length 452 * array if no account was found. This method does not close the cursor. 453 * @param cursor cursor pointing to the list of accounts 454 * @return the array of all accounts stored at this cursor. 455 */ 456 public static Account[] getAllAccounts(ObjectCursor<Account> cursor) { 457 final int initialLength = cursor.getCount(); 458 if (initialLength <= 0 || !cursor.moveToFirst()) { 459 // Return zero length account array rather than null 460 return new Account[0]; 461 } 462 463 final Account[] allAccounts = new Account[initialLength]; 464 int i = 0; 465 do { 466 allAccounts[i++] = cursor.getModel(); 467 } while (cursor.moveToNext()); 468 // Ensure that the length of the array is accurate 469 assert (i == initialLength); 470 return allAccounts; 471 } 472 473 public android.accounts.Account getAccountManagerAccount() { 474 if (amAccount == null) { 475 // We don't really need to synchronize this 476 // as worst case is we'd create an extra identical object and throw it away 477 amAccount = new android.accounts.Account(accountManagerName, type); 478 } 479 return amAccount; 480 } 481 482 public boolean supportsCapability(int capability) { 483 return (capabilities & capability) != 0; 484 } 485 486 public boolean isAccountSyncRequired() { 487 return (syncStatus & SyncStatus.INITIAL_SYNC_NEEDED) == SyncStatus.INITIAL_SYNC_NEEDED; 488 } 489 490 public boolean isAccountInitializationRequired() { 491 return (syncStatus & SyncStatus.ACCOUNT_INITIALIZATION_REQUIRED) == 492 SyncStatus.ACCOUNT_INITIALIZATION_REQUIRED; 493 } 494 495 /** 496 * Returns true when when the UI provider has indicated that the account has been initialized, 497 * and sync is not required. 498 */ 499 public boolean isAccountReady() { 500 return !isAccountInitializationRequired() && !isAccountSyncRequired(); 501 } 502 503 public Account(Parcel in, ClassLoader loader) { 504 name = in.readString(); 505 senderName = in.readString(); 506 type = in.readString(); 507 accountManagerName = in.readString(); 508 providerVersion = in.readInt(); 509 uri = in.readParcelable(null); 510 capabilities = in.readInt(); 511 folderListUri = in.readParcelable(null); 512 fullFolderListUri = in.readParcelable(null); 513 allFolderListUri = in.readParcelable(null); 514 searchUri = in.readParcelable(null); 515 accountFromAddresses = in.readString(); 516 expungeMessageUri = in.readParcelable(null); 517 undoUri = in.readParcelable(null); 518 settingsIntentUri = in.readParcelable(null); 519 helpIntentUri = in.readParcelable(null); 520 sendFeedbackIntentUri = in.readParcelable(null); 521 reauthenticationIntentUri = in.readParcelable(null); 522 syncStatus = in.readInt(); 523 composeIntentUri = in.readParcelable(null); 524 mimeType = in.readString(); 525 recentFolderListUri = in.readParcelable(null); 526 color = in.readInt(); 527 defaultRecentFolderListUri = in.readParcelable(null); 528 manualSyncUri = in.readParcelable(null); 529 viewIntentProxyUri = in.readParcelable(null); 530 accoutCookieQueryUri = in.readParcelable(null); 531 updateSettingsUri = in.readParcelable(null); 532 enableMessageTransforms = in.readInt(); 533 syncAuthority = in.readString(); 534 if (TextUtils.isEmpty(syncAuthority)) { 535 LogUtils.e(LOG_TAG, "Unexpected empty syncAuthority from Parcel"); 536 } 537 quickResponseUri = in.readParcelable(null); 538 final int hasSettings = in.readInt(); 539 if (hasSettings == 0) { 540 LogUtils.e(LOG_TAG, new Throwable(), "Unexpected null settings in Account(Parcel)"); 541 settings = Settings.EMPTY_SETTINGS; 542 } else { 543 settings = in.readParcelable(loader); 544 } 545 } 546 547 @Override 548 public void writeToParcel(Parcel dest, int flags) { 549 dest.writeString(name); 550 dest.writeString(senderName); 551 dest.writeString(type); 552 dest.writeString(accountManagerName); 553 dest.writeInt(providerVersion); 554 dest.writeParcelable(uri, 0); 555 dest.writeInt(capabilities); 556 dest.writeParcelable(folderListUri, 0); 557 dest.writeParcelable(fullFolderListUri, 0); 558 dest.writeParcelable(allFolderListUri, 0); 559 dest.writeParcelable(searchUri, 0); 560 dest.writeString(accountFromAddresses); 561 dest.writeParcelable(expungeMessageUri, 0); 562 dest.writeParcelable(undoUri, 0); 563 dest.writeParcelable(settingsIntentUri, 0); 564 dest.writeParcelable(helpIntentUri, 0); 565 dest.writeParcelable(sendFeedbackIntentUri, 0); 566 dest.writeParcelable(reauthenticationIntentUri, 0); 567 dest.writeInt(syncStatus); 568 dest.writeParcelable(composeIntentUri, 0); 569 dest.writeString(mimeType); 570 dest.writeParcelable(recentFolderListUri, 0); 571 dest.writeInt(color); 572 dest.writeParcelable(defaultRecentFolderListUri, 0); 573 dest.writeParcelable(manualSyncUri, 0); 574 dest.writeParcelable(viewIntentProxyUri, 0); 575 dest.writeParcelable(accoutCookieQueryUri, 0); 576 dest.writeParcelable(updateSettingsUri, 0); 577 dest.writeInt(enableMessageTransforms); 578 dest.writeString(syncAuthority); 579 dest.writeParcelable(quickResponseUri, 0); 580 if (settings == null) { 581 LogUtils.e(LOG_TAG, "unexpected null settings object in writeToParcel"); 582 dest.writeInt(0); 583 } else { 584 dest.writeInt(1); 585 dest.writeParcelable(settings, 0); 586 } 587 } 588 589 @Override 590 public int describeContents() { 591 return 0; 592 } 593 594 @Override 595 public String toString() { 596 // JSON is readable enough. 597 return serialize(); 598 } 599 600 @Override 601 public boolean equals(Object o) { 602 if (o == this) { 603 return true; 604 } 605 606 if ((o == null) || (o.getClass() != this.getClass())) { 607 return false; 608 } 609 610 final Account other = (Account) o; 611 return TextUtils.equals(name, other.name) && 612 TextUtils.equals(senderName, other.senderName) && 613 TextUtils.equals(accountManagerName, other.accountManagerName) && 614 TextUtils.equals(type, other.type) && 615 capabilities == other.capabilities && 616 providerVersion == other.providerVersion && 617 Objects.equal(uri, other.uri) && 618 Objects.equal(folderListUri, other.folderListUri) && 619 Objects.equal(fullFolderListUri, other.fullFolderListUri) && 620 Objects.equal(allFolderListUri, other.allFolderListUri) && 621 Objects.equal(searchUri, other.searchUri) && 622 Objects.equal(accountFromAddresses, other.accountFromAddresses) && 623 Objects.equal(expungeMessageUri, other.expungeMessageUri) && 624 Objects.equal(undoUri, other.undoUri) && 625 Objects.equal(settingsIntentUri, other.settingsIntentUri) && 626 Objects.equal(helpIntentUri, other.helpIntentUri) && 627 Objects.equal(sendFeedbackIntentUri, other.sendFeedbackIntentUri) && 628 Objects.equal(reauthenticationIntentUri, other.reauthenticationIntentUri) && 629 (syncStatus == other.syncStatus) && 630 Objects.equal(composeIntentUri, other.composeIntentUri) && 631 TextUtils.equals(mimeType, other.mimeType) && 632 Objects.equal(recentFolderListUri, other.recentFolderListUri) && 633 color == other.color && 634 Objects.equal(defaultRecentFolderListUri, other.defaultRecentFolderListUri) && 635 Objects.equal(viewIntentProxyUri, other.viewIntentProxyUri) && 636 Objects.equal(accoutCookieQueryUri, other.accoutCookieQueryUri) && 637 Objects.equal(updateSettingsUri, other.updateSettingsUri) && 638 Objects.equal(enableMessageTransforms, other.enableMessageTransforms) && 639 Objects.equal(syncAuthority, other.syncAuthority) && 640 Objects.equal(quickResponseUri, other.quickResponseUri) && 641 Objects.equal(settings, other.settings); 642 } 643 644 /** 645 * Returns true if the two accounts differ in sync or server-side settings. 646 * This is <b>not</b> a replacement for {@link #equals(Object)}. 647 * @param other Account object to compare 648 * @return true if the two accounts differ in sync or server-side settings 649 */ 650 public final boolean settingsDiffer(Account other) { 651 // If the other object doesn't exist, they differ significantly. 652 if (other == null) { 653 return true; 654 } 655 // Check all the server-side settings, the user-side settings and the sync status. 656 return ((this.syncStatus != other.syncStatus) 657 || !Objects.equal(accountFromAddresses, other.accountFromAddresses) 658 || color != other.color 659 || (this.settings.hashCode() != other.settings.hashCode())); 660 } 661 662 @Override 663 public int hashCode() { 664 return Objects.hashCode(name, 665 senderName, 666 accountManagerName, 667 type, 668 capabilities, 669 providerVersion, 670 uri, 671 folderListUri, 672 fullFolderListUri, 673 allFolderListUri, 674 searchUri, 675 accountFromAddresses, 676 expungeMessageUri, 677 undoUri, 678 settingsIntentUri, 679 helpIntentUri, 680 sendFeedbackIntentUri, 681 reauthenticationIntentUri, 682 syncStatus, 683 composeIntentUri, 684 mimeType, 685 recentFolderListUri, 686 color, 687 defaultRecentFolderListUri, 688 viewIntentProxyUri, 689 accoutCookieQueryUri, 690 updateSettingsUri, 691 enableMessageTransforms, 692 syncAuthority, 693 quickResponseUri); 694 } 695 696 /** 697 * Returns whether two Accounts match, as determined by their base URIs. 698 * <p>For a deep object comparison, use {@link #equals(Object)}. 699 * 700 */ 701 public boolean matches(Account other) { 702 return other != null && Objects.equal(uri, other.uri); 703 } 704 705 public List<ReplyFromAccount> getReplyFroms() { 706 707 if (mReplyFroms == null) { 708 mReplyFroms = Lists.newArrayList(); 709 710 // skip if sending is unsupported 711 if (supportsCapability(AccountCapabilities.SENDING_UNAVAILABLE)) { 712 return mReplyFroms; 713 } 714 715 // add the main account address 716 // TODO: name is incorrect here, use senderName once FromAddressSpinner is fixed 717 // b/11292541 718 mReplyFroms.add(new ReplyFromAccount(this, uri, getEmailAddress(), name, 719 getEmailAddress(), false /* isDefault */, false /* isCustom */)); 720 721 if (!TextUtils.isEmpty(accountFromAddresses)) { 722 try { 723 JSONArray accounts = new JSONArray(accountFromAddresses); 724 725 for (int i = 0, len = accounts.length(); i < len; i++) { 726 final ReplyFromAccount a = ReplyFromAccount.deserialize(this, 727 accounts.getJSONObject(i)); 728 if (a != null) { 729 mReplyFroms.add(a); 730 } 731 } 732 733 } catch (JSONException e) { 734 LogUtils.e(LOG_TAG, e, "Unable to parse accountFromAddresses. name=%s", name); 735 } 736 } 737 } 738 return mReplyFroms; 739 } 740 741 /** 742 * @param fromAddress a raw email address, e.g. "user (at) domain.com" 743 * @return if the address belongs to this Account (either as the main address or as a 744 * custom-from) 745 */ 746 public boolean ownsFromAddress(String fromAddress) { 747 for (ReplyFromAccount replyFrom : getReplyFroms()) { 748 if (TextUtils.equals(replyFrom.address, fromAddress)) { 749 return true; 750 } 751 } 752 753 return false; 754 } 755 756 public String getEmailAddress() { 757 return accountManagerName; 758 } 759 760 /** 761 * Returns the real name associated with the account, e.g. "John Doe" or null if no such name 762 * has been configured 763 * @return sender name 764 */ 765 public String getSenderName() { 766 return senderName; 767 } 768 769 @SuppressWarnings("hiding") 770 public static final ClassLoaderCreator<Account> CREATOR = new ClassLoaderCreator<Account>() { 771 @Override 772 public Account createFromParcel(Parcel source, ClassLoader loader) { 773 return new Account(source, loader); 774 } 775 776 @Override 777 public Account createFromParcel(Parcel source) { 778 return new Account(source, null); 779 } 780 781 @Override 782 public Account[] newArray(int size) { 783 return new Account[size]; 784 } 785 }; 786 787 /** 788 * Creates a {@link Map} where the column name is the key and the value is the value, which can 789 * be used for populating a {@link MatrixCursor}. 790 */ 791 public Map<String, Object> getValueMap() { 792 // ImmutableMap.Builder does not allow null values 793 final Map<String, Object> map = new HashMap<String, Object>(); 794 795 map.put(AccountColumns._ID, 0); 796 map.put(AccountColumns.NAME, name); 797 map.put(AccountColumns.SENDER_NAME, senderName); 798 map.put(AccountColumns.TYPE, type); 799 map.put(AccountColumns.ACCOUNT_MANAGER_NAME, accountManagerName); 800 map.put(AccountColumns.PROVIDER_VERSION, providerVersion); 801 map.put(AccountColumns.URI, uri); 802 map.put(AccountColumns.CAPABILITIES, capabilities); 803 map.put(AccountColumns.FOLDER_LIST_URI, folderListUri); 804 map.put(AccountColumns.FULL_FOLDER_LIST_URI, fullFolderListUri); 805 map.put(AccountColumns.ALL_FOLDER_LIST_URI, allFolderListUri); 806 map.put(AccountColumns.SEARCH_URI, searchUri); 807 map.put(AccountColumns.ACCOUNT_FROM_ADDRESSES, accountFromAddresses); 808 map.put(AccountColumns.EXPUNGE_MESSAGE_URI, expungeMessageUri); 809 map.put(AccountColumns.UNDO_URI, undoUri); 810 map.put(AccountColumns.SETTINGS_INTENT_URI, settingsIntentUri); 811 map.put(AccountColumns.HELP_INTENT_URI, helpIntentUri); 812 map.put(AccountColumns.SEND_FEEDBACK_INTENT_URI, sendFeedbackIntentUri); 813 map.put(AccountColumns.REAUTHENTICATION_INTENT_URI, reauthenticationIntentUri); 814 map.put(AccountColumns.SYNC_STATUS, syncStatus); 815 map.put(AccountColumns.COMPOSE_URI, composeIntentUri); 816 map.put(AccountColumns.MIME_TYPE, mimeType); 817 map.put(AccountColumns.RECENT_FOLDER_LIST_URI, recentFolderListUri); 818 map.put(AccountColumns.DEFAULT_RECENT_FOLDER_LIST_URI, defaultRecentFolderListUri); 819 map.put(AccountColumns.MANUAL_SYNC_URI, manualSyncUri); 820 map.put(AccountColumns.VIEW_INTENT_PROXY_URI, viewIntentProxyUri); 821 map.put(AccountColumns.ACCOUNT_COOKIE_QUERY_URI, accoutCookieQueryUri); 822 map.put(AccountColumns.COLOR, color); 823 map.put(AccountColumns.UPDATE_SETTINGS_URI, updateSettingsUri); 824 map.put(AccountColumns.ENABLE_MESSAGE_TRANSFORMS, enableMessageTransforms); 825 map.put(AccountColumns.SYNC_AUTHORITY, syncAuthority); 826 map.put(AccountColumns.QUICK_RESPONSE_URI, quickResponseUri); 827 settings.getValueMap(map); 828 829 return map; 830 } 831 832 /** 833 * Public object that knows how to construct Accounts given Cursors. 834 */ 835 public final static CursorCreator<Account> FACTORY = new CursorCreator<Account>() { 836 @Override 837 public Account createFromCursor(Cursor c) { 838 return new Account(c); 839 } 840 841 @Override 842 public String toString() { 843 return "Account CursorCreator"; 844 } 845 }; 846 } 847