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