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