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