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