Home | History | Annotate | Download | only in providers
      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