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
17package com.android.mail.providers;
18
19import android.content.ContentResolver;
20import android.content.ContentValues;
21import android.database.Cursor;
22import android.database.MatrixCursor;
23import android.net.Uri;
24import android.os.Parcel;
25import android.os.Parcelable;
26import android.text.TextUtils;
27
28import com.android.mail.content.CursorCreator;
29import com.android.mail.content.ObjectCursor;
30import com.android.mail.providers.UIProvider.AccountCapabilities;
31import com.android.mail.providers.UIProvider.AccountColumns;
32import com.android.mail.providers.UIProvider.SyncStatus;
33import com.android.mail.utils.LogTag;
34import com.android.mail.utils.LogUtils;
35import com.android.mail.utils.Utils;
36import com.google.android.mail.common.base.Preconditions;
37import com.google.android.mail.common.base.Strings;
38import com.google.common.base.Objects;
39import com.google.common.collect.Lists;
40
41import org.json.JSONArray;
42import org.json.JSONException;
43import org.json.JSONObject;
44
45import java.util.HashMap;
46import java.util.List;
47import java.util.Map;
48
49public 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@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