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