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.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      */
    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 {
    311         public Account buildFrom(Cursor cursor) {
    312             return new Account(cursor);
    313         }
    314 
    315         public Account buildFrom(JSONObject json) throws JSONException {
    316             return new Account(json);
    317         }
    318 
    319         public Account buildFrom(Parcel in, ClassLoader loader) {
    320             return new Account(in, loader);
    321         }
    322     }
    323 
    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      */
    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      */
    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      */
    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 
    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      */
    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 
    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 
    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      */
    566     public boolean supportsSearch() {
    567         return supportsCapability(AccountCapabilities.LOCAL_SEARCH)
    568                 || supportsCapability(AccountCapabilities.SERVER_SEARCH)
    569                 || supportsCapability(AccountCapabilities.FOLDER_SERVER_SEARCH);
    570     }
    571 
    572     public boolean isAccountSyncRequired() {
    573         return (syncStatus & SyncStatus.INITIAL_SYNC_NEEDED) == SyncStatus.INITIAL_SYNC_NEEDED;
    574     }
    575 
    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      */
    585     public boolean isAccountReady() {
    586         return !isAccountInitializationRequired() && !isAccountSyncRequired();
    587     }
    588 
    589     /**
    590      * @return The account manager account type.
    591      */
    592     public String getType() {
    593         return type;
    594     }
    595 
    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
    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
    691     public int describeContents() {
    692         return 0;
    693     }
    694 
    695     @Override
    696     public String toString() {
    697         // JSON is readable enough.
    698         return serialize();
    699     }
    700 
    701     @Override
    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      */
    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
    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      */
    808     public boolean matches(Account other) {
    809         return other != null && Objects.equal(uri, other.uri);
    810     }
    811 
    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 (at) domain.com"
    849      * @return if the address belongs to this Account (either as the main address or as a
    850      * custom-from)
    851      */
    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      */
    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      */
    881     public String getEmailAddress() {
    882         return accountManagerName;
    883     }
    884 
    885     /**
    886      * The account id is an unique id to represent this account.
    887      */
    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      */
    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      */
    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