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