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