• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.android.emailcommon.provider;
2 
3 import android.content.ContentProviderOperation;
4 import android.content.ContentProviderResult;
5 import android.content.ContentResolver;
6 import android.content.ContentUris;
7 import android.content.ContentValues;
8 import android.content.Context;
9 import android.content.OperationApplicationException;
10 import android.database.Cursor;
11 import android.net.Uri;
12 import android.os.Parcel;
13 import android.os.Parcelable;
14 import android.os.RemoteException;
15 
16 import com.android.emailcommon.provider.EmailContent.AccountColumns;
17 import com.android.emailcommon.utility.Utility;
18 
19 import java.util.ArrayList;
20 import java.util.List;
21 import java.util.UUID;
22 
23 public final class Account extends EmailContent implements AccountColumns, Parcelable {
24     public static final String TABLE_NAME = "Account";
25     @SuppressWarnings("hiding")
26     public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/account");
27     public static final Uri ADD_TO_FIELD_URI =
28         Uri.parse(EmailContent.CONTENT_URI + "/accountIdAddToField");
29     public static final Uri RESET_NEW_MESSAGE_COUNT_URI =
30         Uri.parse(EmailContent.CONTENT_URI + "/resetNewMessageCount");
31     public static final Uri NOTIFIER_URI =
32         Uri.parse(EmailContent.CONTENT_NOTIFIER_URI + "/account");
33     public static final Uri DEFAULT_ACCOUNT_ID_URI =
34         Uri.parse(EmailContent.CONTENT_URI + "/account/default");
35 
36     // Define all pseudo account IDs here to avoid conflict with one another.
37     /**
38      * Pseudo account ID to represent a "combined account" that includes messages and mailboxes
39      * from all defined accounts.
40      *
41      * <em>IMPORTANT</em>: This must never be stored to the database.
42      */
43     public static final long ACCOUNT_ID_COMBINED_VIEW = 0x1000000000000000L;
44     /**
45      * Pseudo account ID to represent "no account". This may be used any time the account ID
46      * may not be known or when we want to specifically select "no" account.
47      *
48      * <em>IMPORTANT</em>: This must never be stored to the database.
49      */
50     public static final long NO_ACCOUNT = -1L;
51 
52     // Whether or not the user has asked for notifications of new mail in this account
53     public final static int FLAGS_NOTIFY_NEW_MAIL = 1<<0;
54     // Whether or not the user has asked for vibration notifications with all new mail
55     public final static int FLAGS_VIBRATE_ALWAYS = 1<<1;
56     // Bit mask for the account's deletion policy (see DELETE_POLICY_x below)
57     public static final int FLAGS_DELETE_POLICY_MASK = 1<<2 | 1<<3;
58     public static final int FLAGS_DELETE_POLICY_SHIFT = 2;
59     // Whether the account is in the process of being created; any account reconciliation code
60     // MUST ignore accounts with this bit set; in addition, ContentObservers for this data
61     // SHOULD consider the state of this flag during operation
62     public static final int FLAGS_INCOMPLETE = 1<<4;
63     // Security hold is used when the device is not in compliance with security policies
64     // required by the server; in this state, the user MUST be alerted to the need to update
65     // security settings.  Sync adapters SHOULD NOT attempt to sync when this flag is set.
66     public static final int FLAGS_SECURITY_HOLD = 1<<5;
67     // Whether or not the user has asked for vibration notifications when the ringer is silent
68     public static final int FLAGS_VIBRATE_WHEN_SILENT = 1<<6;
69     // Whether the account supports "smart forward" (i.e. the server appends the original
70     // message along with any attachments to the outgoing message)
71     public static final int FLAGS_SUPPORTS_SMART_FORWARD = 1<<7;
72     // Whether the account should try to cache attachments in the background
73     public static final int FLAGS_BACKGROUND_ATTACHMENTS = 1<<8;
74     // Available to sync adapter
75     public static final int FLAGS_SYNC_ADAPTER = 1<<9;
76     // Sync disabled is a status commanded by the server; the sync adapter SHOULD NOT try to
77     // sync mailboxes in this account automatically.  A manual sync request to sync a mailbox
78     // with sync disabled SHOULD try to sync and report any failure result via the UI.
79     public static final int FLAGS_SYNC_DISABLED = 1<<10;
80     // Whether or not server-side search is supported by this account
81     public static final int FLAGS_SUPPORTS_SEARCH = 1<<11;
82     // Whether or not server-side search supports global search (i.e. all mailboxes); only valid
83     // if FLAGS_SUPPORTS_SEARCH is true
84     public static final int FLAGS_SUPPORTS_GLOBAL_SEARCH = 1<<12;
85 
86     // Deletion policy (see FLAGS_DELETE_POLICY_MASK, above)
87     public static final int DELETE_POLICY_NEVER = 0;
88     public static final int DELETE_POLICY_7DAYS = 1<<0;        // not supported
89     public static final int DELETE_POLICY_ON_DELETE = 1<<1;
90 
91     // Sentinel values for the mSyncInterval field of both Account records
92     public static final int CHECK_INTERVAL_NEVER = -1;
93     public static final int CHECK_INTERVAL_PUSH = -2;
94 
95     public String mDisplayName;
96     public String mEmailAddress;
97     public String mSyncKey;
98     public int mSyncLookback;
99     public int mSyncInterval;
100     public long mHostAuthKeyRecv;
101     public long mHostAuthKeySend;
102     public int mFlags;
103     public boolean mIsDefault;          // note: callers should use getDefaultAccountId()
104     public String mCompatibilityUuid;
105     public String mSenderName;
106     public String mRingtoneUri;
107     public String mProtocolVersion;
108     public int mNewMessageCount;
109     public String mSecuritySyncKey;
110     public String mSignature;
111     public long mPolicyKey;
112     public long mNotifiedMessageId;
113     public int mNotifiedMessageCount;
114 
115     // Convenience for creating/working with an account
116     public transient HostAuth mHostAuthRecv;
117     public transient HostAuth mHostAuthSend;
118     public transient Policy mPolicy;
119     // Might hold the corresponding AccountManager account structure
120     public transient android.accounts.Account mAmAccount;
121 
122     public static final int CONTENT_ID_COLUMN = 0;
123     public static final int CONTENT_DISPLAY_NAME_COLUMN = 1;
124     public static final int CONTENT_EMAIL_ADDRESS_COLUMN = 2;
125     public static final int CONTENT_SYNC_KEY_COLUMN = 3;
126     public static final int CONTENT_SYNC_LOOKBACK_COLUMN = 4;
127     public static final int CONTENT_SYNC_INTERVAL_COLUMN = 5;
128     public static final int CONTENT_HOST_AUTH_KEY_RECV_COLUMN = 6;
129     public static final int CONTENT_HOST_AUTH_KEY_SEND_COLUMN = 7;
130     public static final int CONTENT_FLAGS_COLUMN = 8;
131     public static final int CONTENT_IS_DEFAULT_COLUMN = 9;
132     public static final int CONTENT_COMPATIBILITY_UUID_COLUMN = 10;
133     public static final int CONTENT_SENDER_NAME_COLUMN = 11;
134     public static final int CONTENT_RINGTONE_URI_COLUMN = 12;
135     public static final int CONTENT_PROTOCOL_VERSION_COLUMN = 13;
136     public static final int CONTENT_NEW_MESSAGE_COUNT_COLUMN = 14;
137     public static final int CONTENT_SECURITY_SYNC_KEY_COLUMN = 15;
138     public static final int CONTENT_SIGNATURE_COLUMN = 16;
139     public static final int CONTENT_POLICY_KEY = 17;
140     public static final int CONTENT_NOTIFIED_MESSAGE_ID = 18;
141     public static final int CONTENT_NOTIFIED_MESSAGE_COUNT = 19;
142 
143     public static final String[] CONTENT_PROJECTION = new String[] {
144         RECORD_ID, AccountColumns.DISPLAY_NAME,
145         AccountColumns.EMAIL_ADDRESS, AccountColumns.SYNC_KEY, AccountColumns.SYNC_LOOKBACK,
146         AccountColumns.SYNC_INTERVAL, AccountColumns.HOST_AUTH_KEY_RECV,
147         AccountColumns.HOST_AUTH_KEY_SEND, AccountColumns.FLAGS, AccountColumns.IS_DEFAULT,
148         AccountColumns.COMPATIBILITY_UUID, AccountColumns.SENDER_NAME,
149         AccountColumns.RINGTONE_URI, AccountColumns.PROTOCOL_VERSION,
150         AccountColumns.NEW_MESSAGE_COUNT, AccountColumns.SECURITY_SYNC_KEY,
151         AccountColumns.SIGNATURE, AccountColumns.POLICY_KEY,
152         AccountColumns.NOTIFIED_MESSAGE_ID, AccountColumns.NOTIFIED_MESSAGE_COUNT
153     };
154 
155     public static final int CONTENT_MAILBOX_TYPE_COLUMN = 1;
156 
157     /**
158      * This projection is for listing account id's only
159      */
160     public static final String[] ID_TYPE_PROJECTION = new String[] {
161         RECORD_ID, MailboxColumns.TYPE
162     };
163 
164     public static final int ACCOUNT_FLAGS_COLUMN_ID = 0;
165     public static final int ACCOUNT_FLAGS_COLUMN_FLAGS = 1;
166     public static final String[] ACCOUNT_FLAGS_PROJECTION = new String[] {
167             AccountColumns.ID, AccountColumns.FLAGS};
168 
169     public static final String MAILBOX_SELECTION =
170         MessageColumns.MAILBOX_KEY + " =?";
171 
172     public static final String UNREAD_COUNT_SELECTION =
173         MessageColumns.MAILBOX_KEY + " =? and " + MessageColumns.FLAG_READ + "= 0";
174 
175     private static final String UUID_SELECTION = AccountColumns.COMPATIBILITY_UUID + " =?";
176 
177     public static final String SECURITY_NONZERO_SELECTION =
178         Account.POLICY_KEY + " IS NOT NULL AND " + Account.POLICY_KEY + "!=0";
179 
180     private static final String FIND_INBOX_SELECTION =
181             MailboxColumns.TYPE + " = " + Mailbox.TYPE_INBOX +
182             " AND " + MailboxColumns.ACCOUNT_KEY + " =?";
183 
184     /**
185      * This projection is for searching for the default account
186      */
187     private static final String[] DEFAULT_ID_PROJECTION = new String[] {
188         RECORD_ID, IS_DEFAULT
189     };
190 
191     /**
192      * no public constructor since this is a utility class
193      */
Account()194     public Account() {
195         mBaseUri = CONTENT_URI;
196 
197         // other defaults (policy)
198         mRingtoneUri = "content://settings/system/notification_sound";
199         mSyncInterval = -1;
200         mSyncLookback = -1;
201         mFlags = FLAGS_NOTIFY_NEW_MAIL;
202         mCompatibilityUuid = UUID.randomUUID().toString();
203     }
204 
restoreAccountWithId(Context context, long id)205     public static Account restoreAccountWithId(Context context, long id) {
206         return EmailContent.restoreContentWithId(context, Account.class,
207                 Account.CONTENT_URI, Account.CONTENT_PROJECTION, id);
208     }
209 
210     /**
211      * Returns {@code true} if the given account ID is a "normal" account. Normal accounts
212      * always have an ID greater than {@code 0} and not equal to any pseudo account IDs
213      * (such as {@link #ACCOUNT_ID_COMBINED_VIEW})
214      */
isNormalAccount(long accountId)215     public static boolean isNormalAccount(long accountId) {
216         return (accountId > 0L) && (accountId != ACCOUNT_ID_COMBINED_VIEW);
217     }
218 
219     /**
220      * Refresh an account that has already been loaded.  This is slightly less expensive
221      * that generating a brand-new account object.
222      */
refresh(Context context)223     public void refresh(Context context) {
224         Cursor c = context.getContentResolver().query(getUri(), Account.CONTENT_PROJECTION,
225                 null, null, null);
226         try {
227             c.moveToFirst();
228             restore(c);
229         } finally {
230             if (c != null) {
231                 c.close();
232             }
233         }
234     }
235 
236     @Override
restore(Cursor cursor)237     public void restore(Cursor cursor) {
238         mId = cursor.getLong(CONTENT_ID_COLUMN);
239         mBaseUri = CONTENT_URI;
240         mDisplayName = cursor.getString(CONTENT_DISPLAY_NAME_COLUMN);
241         mEmailAddress = cursor.getString(CONTENT_EMAIL_ADDRESS_COLUMN);
242         mSyncKey = cursor.getString(CONTENT_SYNC_KEY_COLUMN);
243         mSyncLookback = cursor.getInt(CONTENT_SYNC_LOOKBACK_COLUMN);
244         mSyncInterval = cursor.getInt(CONTENT_SYNC_INTERVAL_COLUMN);
245         mHostAuthKeyRecv = cursor.getLong(CONTENT_HOST_AUTH_KEY_RECV_COLUMN);
246         mHostAuthKeySend = cursor.getLong(CONTENT_HOST_AUTH_KEY_SEND_COLUMN);
247         mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN);
248         mIsDefault = cursor.getInt(CONTENT_IS_DEFAULT_COLUMN) == 1;
249         mCompatibilityUuid = cursor.getString(CONTENT_COMPATIBILITY_UUID_COLUMN);
250         mSenderName = cursor.getString(CONTENT_SENDER_NAME_COLUMN);
251         mRingtoneUri = cursor.getString(CONTENT_RINGTONE_URI_COLUMN);
252         mProtocolVersion = cursor.getString(CONTENT_PROTOCOL_VERSION_COLUMN);
253         mNewMessageCount = cursor.getInt(CONTENT_NEW_MESSAGE_COUNT_COLUMN);
254         mSecuritySyncKey = cursor.getString(CONTENT_SECURITY_SYNC_KEY_COLUMN);
255         mSignature = cursor.getString(CONTENT_SIGNATURE_COLUMN);
256         mPolicyKey = cursor.getLong(CONTENT_POLICY_KEY);
257         mNotifiedMessageId = cursor.getLong(CONTENT_NOTIFIED_MESSAGE_ID);
258         mNotifiedMessageCount = cursor.getInt(CONTENT_NOTIFIED_MESSAGE_COUNT);
259     }
260 
getId(Uri u)261     private long getId(Uri u) {
262         return Long.parseLong(u.getPathSegments().get(1));
263     }
264 
265     /**
266      * @return the user-visible name for the account
267      */
getDisplayName()268     public String getDisplayName() {
269         return mDisplayName;
270     }
271 
272     /**
273      * Set the description.  Be sure to call save() to commit to database.
274      * @param description the new description
275      */
setDisplayName(String description)276     public void setDisplayName(String description) {
277         mDisplayName = description;
278     }
279 
280     /**
281      * @return the email address for this account
282      */
getEmailAddress()283     public String getEmailAddress() {
284         return mEmailAddress;
285     }
286 
287     /**
288      * Set the Email address for this account.  Be sure to call save() to commit to database.
289      * @param emailAddress the new email address for this account
290      */
setEmailAddress(String emailAddress)291     public void setEmailAddress(String emailAddress) {
292         mEmailAddress = emailAddress;
293     }
294 
295     /**
296      * @return the sender's name for this account
297      */
getSenderName()298     public String getSenderName() {
299         return mSenderName;
300     }
301 
302     /**
303      * Set the sender's name.  Be sure to call save() to commit to database.
304      * @param name the new sender name
305      */
setSenderName(String name)306     public void setSenderName(String name) {
307         mSenderName = name;
308     }
309 
getSignature()310     public String getSignature() {
311         return mSignature;
312     }
313 
setSignature(String signature)314     public void setSignature(String signature) {
315         mSignature = signature;
316     }
317 
318     /**
319      * @return the minutes per check (for polling)
320      * TODO define sentinel values for "never", "push", etc.  See Account.java
321      */
getSyncInterval()322     public int getSyncInterval() {
323         return mSyncInterval;
324     }
325 
326     /**
327      * Set the minutes per check (for polling).  Be sure to call save() to commit to database.
328      * TODO define sentinel values for "never", "push", etc.  See Account.java
329      * @param minutes the number of minutes between polling checks
330      */
setSyncInterval(int minutes)331     public void setSyncInterval(int minutes) {
332         mSyncInterval = minutes;
333     }
334 
335     /**
336      * @return One of the {@code Account.SYNC_WINDOW_*} constants that represents the sync
337      *     lookback window.
338      * TODO define sentinel values for "all", "1 month", etc.  See Account.java
339      */
getSyncLookback()340     public int getSyncLookback() {
341         return mSyncLookback;
342     }
343 
344     /**
345      * Set the sync lookback window.  Be sure to call save() to commit to database.
346      * TODO define sentinel values for "all", "1 month", etc.  See Account.java
347      * @param value One of the {@link com.android.emailcommon.service.SyncWindow} constants
348      */
setSyncLookback(int value)349     public void setSyncLookback(int value) {
350         mSyncLookback = value;
351     }
352 
353     /**
354      * @return the flags for this account
355      * @see #FLAGS_NOTIFY_NEW_MAIL
356      * @see #FLAGS_VIBRATE_ALWAYS
357      * @see #FLAGS_VIBRATE_WHEN_SILENT
358      */
getFlags()359     public int getFlags() {
360         return mFlags;
361     }
362 
363     /**
364      * Set the flags for this account
365      * @see #FLAGS_NOTIFY_NEW_MAIL
366      * @see #FLAGS_VIBRATE_ALWAYS
367      * @see #FLAGS_VIBRATE_WHEN_SILENT
368      * @param newFlags the new value for the flags
369      */
setFlags(int newFlags)370     public void setFlags(int newFlags) {
371         mFlags = newFlags;
372     }
373 
374     /**
375      * @return the ringtone Uri for this account
376      */
getRingtone()377     public String getRingtone() {
378         return mRingtoneUri;
379     }
380 
381     /**
382      * Set the ringtone Uri for this account
383      * @param newUri the new URI string for the ringtone for this account
384      */
setRingtone(String newUri)385     public void setRingtone(String newUri) {
386         mRingtoneUri = newUri;
387     }
388 
389     /**
390      * Set the "delete policy" as a simple 0,1,2 value set.
391      * @param newPolicy the new delete policy
392      */
setDeletePolicy(int newPolicy)393     public void setDeletePolicy(int newPolicy) {
394         mFlags &= ~FLAGS_DELETE_POLICY_MASK;
395         mFlags |= (newPolicy << FLAGS_DELETE_POLICY_SHIFT) & FLAGS_DELETE_POLICY_MASK;
396     }
397 
398     /**
399      * Return the "delete policy" as a simple 0,1,2 value set.
400      * @return the current delete policy
401      */
getDeletePolicy()402     public int getDeletePolicy() {
403         return (mFlags & FLAGS_DELETE_POLICY_MASK) >> FLAGS_DELETE_POLICY_SHIFT;
404     }
405 
406     /**
407      * Return the Uuid associated with this account.  This is primarily for compatibility
408      * with accounts set up by previous versions, because there are externals references
409      * to the Uuid (e.g. desktop shortcuts).
410      */
getUuid()411     public String getUuid() {
412         return mCompatibilityUuid;
413     }
414 
getOrCreateHostAuthSend(Context context)415     public HostAuth getOrCreateHostAuthSend(Context context) {
416         if (mHostAuthSend == null) {
417             if (mHostAuthKeySend != 0) {
418                 mHostAuthSend = HostAuth.restoreHostAuthWithId(context, mHostAuthKeySend);
419             } else {
420                 mHostAuthSend = new HostAuth();
421             }
422         }
423         return mHostAuthSend;
424     }
425 
getOrCreateHostAuthRecv(Context context)426     public HostAuth getOrCreateHostAuthRecv(Context context) {
427         if (mHostAuthRecv == null) {
428             if (mHostAuthKeyRecv != 0) {
429                 mHostAuthRecv = HostAuth.restoreHostAuthWithId(context, mHostAuthKeyRecv);
430             } else {
431                 mHostAuthRecv = new HostAuth();
432             }
433         }
434         return mHostAuthRecv;
435     }
436 
437     /**
438      * For compatibility while converting to provider model, generate a "local store URI"
439      *
440      * @return a string in the form of a Uri, as used by the other parts of the email app
441      */
getLocalStoreUri(Context context)442     public String getLocalStoreUri(Context context) {
443         return "local://localhost/" + context.getDatabasePath(getUuid() + ".db");
444     }
445 
446     /**
447      * @return true if the instance is of an EAS account.
448      *
449      * NOTE This method accesses the DB if {@link #mHostAuthRecv} hasn't been restored yet.
450      * Use caution when you use this on the main thread.
451      */
isEasAccount(Context context)452     public boolean isEasAccount(Context context) {
453         return "eas".equals(getProtocol(context));
454     }
455 
supportsMoveMessages(Context context)456     public boolean supportsMoveMessages(Context context) {
457         String protocol = getProtocol(context);
458         return "eas".equals(protocol) || "imap".equals(protocol);
459     }
460 
461     /**
462      * @return true if the account supports "search".
463      */
supportsServerSearch(Context context, long accountId)464     public static boolean supportsServerSearch(Context context, long accountId) {
465         Account account = Account.restoreAccountWithId(context, accountId);
466         if (account == null) return false;
467         return (account.mFlags & Account.FLAGS_SUPPORTS_SEARCH) != 0;
468     }
469 
470     /**
471      * Set the account to be the default account.  If this is set to "true", when the account
472      * is saved, all other accounts will have the same value set to "false".
473      * @param newDefaultState the new default state - if true, others will be cleared.
474      */
setDefaultAccount(boolean newDefaultState)475     public void setDefaultAccount(boolean newDefaultState) {
476         mIsDefault = newDefaultState;
477     }
478 
479     /**
480      * @return {@link Uri} to this {@link Account} in the
481      * {@code content://com.android.email.provider/account/UUID} format, which is safe to use
482      * for desktop shortcuts.
483      *
484      * <p>We don't want to store _id in shortcuts, because
485      * {@link com.android.email.provider.AccountBackupRestore} won't preserve it.
486      */
getShortcutSafeUri()487     public Uri getShortcutSafeUri() {
488         return getShortcutSafeUriFromUuid(mCompatibilityUuid);
489     }
490 
491     /**
492      * @return {@link Uri} to an {@link Account} with a {@code uuid}.
493      */
getShortcutSafeUriFromUuid(String uuid)494     public static Uri getShortcutSafeUriFromUuid(String uuid) {
495         return CONTENT_URI.buildUpon().appendEncodedPath(uuid).build();
496     }
497 
498     /**
499      * Parse {@link Uri} in the {@code content://com.android.email.provider/account/ID} format
500      * where ID = account id (used on Eclair, Android 2.0-2.1) or UUID, and return _id of
501      * the {@link Account} associated with it.
502      *
503      * @param context context to access DB
504      * @param uri URI of interest
505      * @return _id of the {@link Account} associated with ID, or -1 if none found.
506      */
getAccountIdFromShortcutSafeUri(Context context, Uri uri)507     public static long getAccountIdFromShortcutSafeUri(Context context, Uri uri) {
508         // Make sure the URI is in the correct format.
509         if (!"content".equals(uri.getScheme())
510                 || !AUTHORITY.equals(uri.getAuthority())) {
511             return -1;
512         }
513 
514         final List<String> ps = uri.getPathSegments();
515         if (ps.size() != 2 || !"account".equals(ps.get(0))) {
516             return -1;
517         }
518 
519         // Now get the ID part.
520         final String id = ps.get(1);
521 
522         // First, see if ID can be parsed as long.  (Eclair-style)
523         // (UUIDs have '-' in them, so they are always non-parsable.)
524         try {
525             return Long.parseLong(id);
526         } catch (NumberFormatException ok) {
527             // OK, it's not a long.  Continue...
528         }
529 
530         // Now id is a UUId.
531         return getAccountIdFromUuid(context, id);
532     }
533 
534     /**
535      * @return ID of the account with the given UUID.
536      */
getAccountIdFromUuid(Context context, String uuid)537     public static long getAccountIdFromUuid(Context context, String uuid) {
538         return Utility.getFirstRowLong(context,
539                 CONTENT_URI, ID_PROJECTION,
540                 UUID_SELECTION, new String[] {uuid}, null, 0, -1L);
541     }
542 
543     /**
544      * Return the id of the default account.  If one hasn't been explicitly specified, return
545      * the first one in the database (the logic is provided within EmailProvider)
546      * @param context the caller's context
547      * @return the id of the default account, or Account.NO_ACCOUNT if there are no accounts
548      */
getDefaultAccountId(Context context)549     static public long getDefaultAccountId(Context context) {
550         Cursor c = context.getContentResolver().query(
551                 Account.DEFAULT_ACCOUNT_ID_URI, Account.ID_PROJECTION, null, null, null);
552         try {
553             if (c != null && c.moveToFirst()) {
554                 return c.getLong(Account.ID_PROJECTION_COLUMN);
555             }
556         } finally {
557             c.close();
558         }
559         return Account.NO_ACCOUNT;
560     }
561 
562     /**
563      * Given an account id, return the account's protocol
564      * @param context the caller's context
565      * @param accountId the id of the account to be examined
566      * @return the account's protocol (or null if the Account or HostAuth do not exist)
567      */
getProtocol(Context context, long accountId)568     public static String getProtocol(Context context, long accountId) {
569         Account account = Account.restoreAccountWithId(context, accountId);
570         if (account != null) {
571             return account.getProtocol(context);
572          }
573         return null;
574     }
575 
576     /**
577      * Return the account's protocol
578      * @param context the caller's context
579      * @return the account's protocol (or null if the HostAuth doesn't not exist)
580      */
getProtocol(Context context)581     public String getProtocol(Context context) {
582         HostAuth hostAuth = HostAuth.restoreHostAuthWithId(context, mHostAuthKeyRecv);
583         if (hostAuth != null) {
584             return hostAuth.mProtocol;
585         }
586         return null;
587     }
588 
589     /**
590      * Return the account ID for a message with a given id
591      *
592      * @param context the caller's context
593      * @param messageId the id of the message
594      * @return the account ID, or -1 if the account doesn't exist
595      */
getAccountIdForMessageId(Context context, long messageId)596     public static long getAccountIdForMessageId(Context context, long messageId) {
597         return Message.getKeyColumnLong(context, messageId, MessageColumns.ACCOUNT_KEY);
598     }
599 
600     /**
601      * Return the account for a message with a given id
602      * @param context the caller's context
603      * @param messageId the id of the message
604      * @return the account, or null if the account doesn't exist
605      */
getAccountForMessageId(Context context, long messageId)606     public static Account getAccountForMessageId(Context context, long messageId) {
607         long accountId = getAccountIdForMessageId(context, messageId);
608         if (accountId != -1) {
609             return Account.restoreAccountWithId(context, accountId);
610         }
611         return null;
612     }
613 
614     /**
615      * @return true if an {@code accountId} is assigned to any existing account.
616      */
isValidId(Context context, long accountId)617     public static boolean isValidId(Context context, long accountId) {
618         return null != Utility.getFirstRowLong(context, CONTENT_URI, ID_PROJECTION,
619                 ID_SELECTION, new String[] {Long.toString(accountId)}, null,
620                 ID_PROJECTION_COLUMN);
621     }
622 
623     /**
624      * Check a single account for security hold status.
625      */
isSecurityHold(Context context, long accountId)626     public static boolean isSecurityHold(Context context, long accountId) {
627         return (Utility.getFirstRowLong(context,
628                 ContentUris.withAppendedId(Account.CONTENT_URI, accountId),
629                 ACCOUNT_FLAGS_PROJECTION, null, null, null, ACCOUNT_FLAGS_COLUMN_FLAGS, 0L)
630                 & Account.FLAGS_SECURITY_HOLD) != 0;
631     }
632 
633     /**
634      * @return id of the "inbox" mailbox, or -1 if not found.
635      */
getInboxId(Context context, long accountId)636     public static long getInboxId(Context context, long accountId) {
637         return Utility.getFirstRowLong(context, Mailbox.CONTENT_URI, ID_PROJECTION,
638                 FIND_INBOX_SELECTION, new String[] {Long.toString(accountId)}, null,
639                 ID_PROJECTION_COLUMN, -1L);
640     }
641 
642     /**
643      * Clear all account hold flags that are set.
644      *
645      * (This will trigger watchers, and in particular will cause EAS to try and resync the
646      * account(s).)
647      */
clearSecurityHoldOnAllAccounts(Context context)648     public static void clearSecurityHoldOnAllAccounts(Context context) {
649         ContentResolver resolver = context.getContentResolver();
650         Cursor c = resolver.query(Account.CONTENT_URI, ACCOUNT_FLAGS_PROJECTION,
651                 SECURITY_NONZERO_SELECTION, null, null);
652         try {
653             while (c.moveToNext()) {
654                 int flags = c.getInt(ACCOUNT_FLAGS_COLUMN_FLAGS);
655 
656                 if (0 != (flags & FLAGS_SECURITY_HOLD)) {
657                     ContentValues cv = new ContentValues();
658                     cv.put(AccountColumns.FLAGS, flags & ~FLAGS_SECURITY_HOLD);
659                     long accountId = c.getLong(ACCOUNT_FLAGS_COLUMN_ID);
660                     Uri uri = ContentUris.withAppendedId(Account.CONTENT_URI, accountId);
661                     resolver.update(uri, cv, null, null);
662                 }
663             }
664         } finally {
665             c.close();
666         }
667     }
668 
669     /**
670      * Override update to enforce a single default account, and do it atomically
671      */
672     @Override
update(Context context, ContentValues cv)673     public int update(Context context, ContentValues cv) {
674         if (mPolicy != null && mPolicyKey <= 0) {
675             // If a policy is set and there's no policy, link it to the account
676             Policy.setAccountPolicy(context, this, mPolicy, null);
677         }
678         if (cv.containsKey(AccountColumns.IS_DEFAULT) &&
679                 cv.getAsBoolean(AccountColumns.IS_DEFAULT)) {
680             ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
681             ContentValues cv1 = new ContentValues();
682             cv1.put(AccountColumns.IS_DEFAULT, false);
683             // Clear the default flag in all accounts
684             ops.add(ContentProviderOperation.newUpdate(CONTENT_URI).withValues(cv1).build());
685             // Update this account
686             ops.add(ContentProviderOperation
687                     .newUpdate(ContentUris.withAppendedId(CONTENT_URI, mId))
688                     .withValues(cv).build());
689             try {
690                 context.getContentResolver().applyBatch(AUTHORITY, ops);
691                 return 1;
692             } catch (RemoteException e) {
693                 // There is nothing to be done here; fail by returning 0
694             } catch (OperationApplicationException e) {
695                 // There is nothing to be done here; fail by returning 0
696             }
697             return 0;
698         }
699         return super.update(context, cv);
700     }
701 
702     /*
703      * Override this so that we can store the HostAuth's first and link them to the Account
704      * (non-Javadoc)
705      * @see com.android.email.provider.EmailContent#save(android.content.Context)
706      */
707     @Override
save(Context context)708     public Uri save(Context context) {
709         if (isSaved()) {
710             throw new UnsupportedOperationException();
711         }
712         // This logic is in place so I can (a) short circuit the expensive stuff when
713         // possible, and (b) override (and throw) if anyone tries to call save() or update()
714         // directly for Account, which are unsupported.
715         if (mHostAuthRecv == null && mHostAuthSend == null && mIsDefault == false &&
716                 mPolicy != null) {
717             return super.save(context);
718         }
719 
720         int index = 0;
721         int recvIndex = -1;
722         int sendIndex = -1;
723         int policyIndex = -1;
724 
725         // Create operations for saving the send and recv hostAuths
726         // Also, remember which operation in the array they represent
727         ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
728         if (mHostAuthRecv != null) {
729             recvIndex = index++;
730             ops.add(ContentProviderOperation.newInsert(mHostAuthRecv.mBaseUri)
731                     .withValues(mHostAuthRecv.toContentValues())
732                     .build());
733         }
734         if (mHostAuthSend != null) {
735             sendIndex = index++;
736             ops.add(ContentProviderOperation.newInsert(mHostAuthSend.mBaseUri)
737                     .withValues(mHostAuthSend.toContentValues())
738                     .build());
739         }
740         if (mPolicy != null) {
741             policyIndex = index++;
742             ops.add(ContentProviderOperation.newInsert(mPolicy.mBaseUri)
743                     .withValues(mPolicy.toContentValues())
744                     .build());
745         }
746 
747         // Create operations for making this the only default account
748         // Note, these are always updates because they change existing accounts
749         if (mIsDefault) {
750             index++;
751             ContentValues cv1 = new ContentValues();
752             cv1.put(AccountColumns.IS_DEFAULT, 0);
753             ops.add(ContentProviderOperation.newUpdate(CONTENT_URI).withValues(cv1).build());
754         }
755 
756         // Now do the Account
757         ContentValues cv = null;
758         if (recvIndex >= 0 || sendIndex >= 0 || policyIndex >= 0) {
759             cv = new ContentValues();
760             if (recvIndex >= 0) {
761                 cv.put(Account.HOST_AUTH_KEY_RECV, recvIndex);
762             }
763             if (sendIndex >= 0) {
764                 cv.put(Account.HOST_AUTH_KEY_SEND, sendIndex);
765             }
766             if (policyIndex >= 0) {
767                 cv.put(Account.POLICY_KEY, policyIndex);
768             }
769         }
770 
771         ContentProviderOperation.Builder b = ContentProviderOperation.newInsert(mBaseUri);
772         b.withValues(toContentValues());
773         if (cv != null) {
774             b.withValueBackReferences(cv);
775         }
776         ops.add(b.build());
777 
778         try {
779             ContentProviderResult[] results =
780                 context.getContentResolver().applyBatch(AUTHORITY, ops);
781             // If saving, set the mId's of the various saved objects
782             if (recvIndex >= 0) {
783                 long newId = getId(results[recvIndex].uri);
784                 mHostAuthKeyRecv = newId;
785                 mHostAuthRecv.mId = newId;
786             }
787             if (sendIndex >= 0) {
788                 long newId = getId(results[sendIndex].uri);
789                 mHostAuthKeySend = newId;
790                 mHostAuthSend.mId = newId;
791             }
792             if (policyIndex >= 0) {
793                 long newId = getId(results[policyIndex].uri);
794                 mPolicyKey = newId;
795                 mPolicy.mId = newId;
796             }
797             Uri u = results[index].uri;
798             mId = getId(u);
799             return u;
800         } catch (RemoteException e) {
801             // There is nothing to be done here; fail by returning null
802         } catch (OperationApplicationException e) {
803             // There is nothing to be done here; fail by returning null
804         }
805         return null;
806     }
807 
808     @Override
toContentValues()809     public ContentValues toContentValues() {
810         ContentValues values = new ContentValues();
811         values.put(AccountColumns.DISPLAY_NAME, mDisplayName);
812         values.put(AccountColumns.EMAIL_ADDRESS, mEmailAddress);
813         values.put(AccountColumns.SYNC_KEY, mSyncKey);
814         values.put(AccountColumns.SYNC_LOOKBACK, mSyncLookback);
815         values.put(AccountColumns.SYNC_INTERVAL, mSyncInterval);
816         values.put(AccountColumns.HOST_AUTH_KEY_RECV, mHostAuthKeyRecv);
817         values.put(AccountColumns.HOST_AUTH_KEY_SEND, mHostAuthKeySend);
818         values.put(AccountColumns.FLAGS, mFlags);
819         values.put(AccountColumns.IS_DEFAULT, mIsDefault);
820         values.put(AccountColumns.COMPATIBILITY_UUID, mCompatibilityUuid);
821         values.put(AccountColumns.SENDER_NAME, mSenderName);
822         values.put(AccountColumns.RINGTONE_URI, mRingtoneUri);
823         values.put(AccountColumns.PROTOCOL_VERSION, mProtocolVersion);
824         values.put(AccountColumns.NEW_MESSAGE_COUNT, mNewMessageCount);
825         values.put(AccountColumns.SECURITY_SYNC_KEY, mSecuritySyncKey);
826         values.put(AccountColumns.SIGNATURE, mSignature);
827         values.put(AccountColumns.POLICY_KEY, mPolicyKey);
828         values.put(AccountColumns.NOTIFIED_MESSAGE_ID, mNotifiedMessageId);
829         values.put(AccountColumns.NOTIFIED_MESSAGE_COUNT, mNotifiedMessageCount);
830         return values;
831     }
832 
833     /**
834      * Supports Parcelable
835      */
836     @Override
describeContents()837     public int describeContents() {
838         return 0;
839     }
840 
841     /**
842      * Supports Parcelable
843      */
844     public static final Parcelable.Creator<Account> CREATOR
845             = new Parcelable.Creator<Account>() {
846         @Override
847         public Account createFromParcel(Parcel in) {
848             return new Account(in);
849         }
850 
851         @Override
852         public Account[] newArray(int size) {
853             return new Account[size];
854         }
855     };
856 
857     /**
858      * Supports Parcelable
859      */
860     @Override
writeToParcel(Parcel dest, int flags)861     public void writeToParcel(Parcel dest, int flags) {
862         // mBaseUri is not parceled
863         dest.writeLong(mId);
864         dest.writeString(mDisplayName);
865         dest.writeString(mEmailAddress);
866         dest.writeString(mSyncKey);
867         dest.writeInt(mSyncLookback);
868         dest.writeInt(mSyncInterval);
869         dest.writeLong(mHostAuthKeyRecv);
870         dest.writeLong(mHostAuthKeySend);
871         dest.writeInt(mFlags);
872         dest.writeByte(mIsDefault ? (byte)1 : (byte)0);
873         dest.writeString(mCompatibilityUuid);
874         dest.writeString(mSenderName);
875         dest.writeString(mRingtoneUri);
876         dest.writeString(mProtocolVersion);
877         dest.writeInt(mNewMessageCount);
878         dest.writeString(mSecuritySyncKey);
879         dest.writeString(mSignature);
880         dest.writeLong(mPolicyKey);
881         dest.writeLong(mNotifiedMessageId);
882         dest.writeInt(mNotifiedMessageCount);
883 
884         if (mHostAuthRecv != null) {
885             dest.writeByte((byte)1);
886             mHostAuthRecv.writeToParcel(dest, flags);
887         } else {
888             dest.writeByte((byte)0);
889         }
890 
891         if (mHostAuthSend != null) {
892             dest.writeByte((byte)1);
893             mHostAuthSend.writeToParcel(dest, flags);
894         } else {
895             dest.writeByte((byte)0);
896         }
897     }
898 
899     /**
900      * Supports Parcelable
901      */
Account(Parcel in)902     public Account(Parcel in) {
903         mBaseUri = Account.CONTENT_URI;
904         mId = in.readLong();
905         mDisplayName = in.readString();
906         mEmailAddress = in.readString();
907         mSyncKey = in.readString();
908         mSyncLookback = in.readInt();
909         mSyncInterval = in.readInt();
910         mHostAuthKeyRecv = in.readLong();
911         mHostAuthKeySend = in.readLong();
912         mFlags = in.readInt();
913         mIsDefault = in.readByte() == 1;
914         mCompatibilityUuid = in.readString();
915         mSenderName = in.readString();
916         mRingtoneUri = in.readString();
917         mProtocolVersion = in.readString();
918         mNewMessageCount = in.readInt();
919         mSecuritySyncKey = in.readString();
920         mSignature = in.readString();
921         mPolicyKey = in.readLong();
922         mNotifiedMessageId = in.readLong();
923         mNotifiedMessageCount = in.readInt();
924 
925         mHostAuthRecv = null;
926         if (in.readByte() == 1) {
927             mHostAuthRecv = new HostAuth(in);
928         }
929 
930         mHostAuthSend = null;
931         if (in.readByte() == 1) {
932             mHostAuthSend = new HostAuth(in);
933         }
934     }
935 
936     /**
937      * For debugger support only - DO NOT use for code.
938      */
939     @Override
toString()940     public String toString() {
941         StringBuilder sb = new StringBuilder('[');
942         if (mHostAuthRecv != null && mHostAuthRecv.mProtocol != null) {
943             sb.append(mHostAuthRecv.mProtocol);
944             sb.append(':');
945         }
946         if (mDisplayName != null)   sb.append(mDisplayName);
947         sb.append(':');
948         if (mEmailAddress != null)  sb.append(mEmailAddress);
949         sb.append(':');
950         if (mSenderName != null)    sb.append(mSenderName);
951         sb.append(']');
952         return sb.toString();
953     }
954 
955 }