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