Account.java revision aca94265813e72e692eace527f43eb4c02b09c76
1package com.android.emailcommon.provider;
2
3import android.content.ContentProviderOperation;
4import android.content.ContentProviderResult;
5import android.content.ContentResolver;
6import android.content.ContentUris;
7import android.content.ContentValues;
8import android.content.Context;
9import android.content.OperationApplicationException;
10import android.database.Cursor;
11import android.net.Uri;
12import android.os.Parcel;
13import android.os.Parcelable;
14import android.os.RemoteException;
15
16import com.android.emailcommon.provider.EmailContent.AccountColumns;
17import com.android.emailcommon.utility.Utility;
18
19import java.util.ArrayList;
20import java.util.List;
21import java.util.UUID;
22
23public 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     */
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
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     */
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     */
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
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
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     */
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     */
276    public void setDisplayName(String description) {
277        mDisplayName = description;
278    }
279
280    /**
281     * @return the email address for this account
282     */
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     */
291    public void setEmailAddress(String emailAddress) {
292        mEmailAddress = emailAddress;
293    }
294
295    /**
296     * @return the sender's name for this account
297     */
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     */
306    public void setSenderName(String name) {
307        mSenderName = name;
308    }
309
310    public String getSignature() {
311        return mSignature;
312    }
313
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     */
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     */
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     */
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     */
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     */
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     */
370    public void setFlags(int newFlags) {
371        mFlags = newFlags;
372    }
373
374    /**
375     * @return the ringtone Uri for this account
376     */
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     */
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     */
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     */
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     */
411    public String getUuid() {
412        return mCompatibilityUuid;
413    }
414
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
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     */
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     */
452    public boolean isEasAccount(Context context) {
453        return "eas".equals(getProtocol(context));
454    }
455
456    /**
457     * @return true if the account supports "move messages".
458     */
459    public static boolean supportsMoveMessages(Context context, long accountId) {
460        String protocol = getProtocol(context, accountId);
461        return "eas".equals(protocol) || "imap".equals(protocol);
462    }
463
464    /**
465     * @return true if the account supports "search".
466     */
467    public static boolean supportsServerSearch(Context context, long accountId) {
468        Account account = Account.restoreAccountWithId(context, accountId);
469        if (account == null) return false;
470        return (account.mFlags & Account.FLAGS_SUPPORTS_SEARCH) != 0;
471    }
472
473    /**
474     * Set the account to be the default account.  If this is set to "true", when the account
475     * is saved, all other accounts will have the same value set to "false".
476     * @param newDefaultState the new default state - if true, others will be cleared.
477     */
478    public void setDefaultAccount(boolean newDefaultState) {
479        mIsDefault = newDefaultState;
480    }
481
482    /**
483     * Helper method for finding the default account.
484     */
485    static private long getDefaultAccountWhere(Context context, String where) {
486        return Utility.getFirstRowLong(context, CONTENT_URI,
487                DEFAULT_ID_PROJECTION,
488                where, null, null, 0, Long.valueOf(-1));
489    }
490
491    /**
492     * @return {@link Uri} to this {@link Account} in the
493     * {@code content://com.android.email.provider/account/UUID} format, which is safe to use
494     * for desktop shortcuts.
495     *
496     * <p>We don't want to store _id in shortcuts, because
497     * {@link com.android.email.provider.AccountBackupRestore} won't preserve it.
498     */
499    public Uri getShortcutSafeUri() {
500        return getShortcutSafeUriFromUuid(mCompatibilityUuid);
501    }
502
503    /**
504     * @return {@link Uri} to an {@link Account} with a {@code uuid}.
505     */
506    public static Uri getShortcutSafeUriFromUuid(String uuid) {
507        return CONTENT_URI.buildUpon().appendEncodedPath(uuid).build();
508    }
509
510    /**
511     * Parse {@link Uri} in the {@code content://com.android.email.provider/account/ID} format
512     * where ID = account id (used on Eclair, Android 2.0-2.1) or UUID, and return _id of
513     * the {@link Account} associated with it.
514     *
515     * @param context context to access DB
516     * @param uri URI of interest
517     * @return _id of the {@link Account} associated with ID, or -1 if none found.
518     */
519    public static long getAccountIdFromShortcutSafeUri(Context context, Uri uri) {
520        // Make sure the URI is in the correct format.
521        if (!"content".equals(uri.getScheme())
522                || !AUTHORITY.equals(uri.getAuthority())) {
523            return -1;
524        }
525
526        final List<String> ps = uri.getPathSegments();
527        if (ps.size() != 2 || !"account".equals(ps.get(0))) {
528            return -1;
529        }
530
531        // Now get the ID part.
532        final String id = ps.get(1);
533
534        // First, see if ID can be parsed as long.  (Eclair-style)
535        // (UUIDs have '-' in them, so they are always non-parsable.)
536        try {
537            return Long.parseLong(id);
538        } catch (NumberFormatException ok) {
539            // OK, it's not a long.  Continue...
540        }
541
542        // Now id is a UUId.
543        return getAccountIdFromUuid(context, id);
544    }
545
546    /**
547     * @return ID of the account with the given UUID.
548     */
549    public static long getAccountIdFromUuid(Context context, String uuid) {
550        return Utility.getFirstRowLong(context,
551                CONTENT_URI, ID_PROJECTION,
552                UUID_SELECTION, new String[] {uuid}, null, 0, -1L);
553    }
554
555    /**
556     * Return the id of the default account.  If one hasn't been explicitly specified, return
557     * the first one in the database (the logic is provided within EmailProvider)
558     * @param context the caller's context
559     * @return the id of the default account, or Account.NO_ACCOUNT if there are no accounts
560     */
561    static public long getDefaultAccountId(Context context) {
562        Cursor c = context.getContentResolver().query(
563                Account.DEFAULT_ACCOUNT_ID_URI, Account.ID_PROJECTION, null, null, null);
564        try {
565            if (c != null && c.moveToFirst()) {
566                return c.getLong(Account.ID_PROJECTION_COLUMN);
567            }
568        } finally {
569            c.close();
570        }
571        return Account.NO_ACCOUNT;
572    }
573
574    /**
575     * Given an account id, return the account's protocol
576     * @param context the caller's context
577     * @param accountId the id of the account to be examined
578     * @return the account's protocol (or null if the Account or HostAuth do not exist)
579     */
580    public static String getProtocol(Context context, long accountId) {
581        Account account = Account.restoreAccountWithId(context, accountId);
582        if (account != null) {
583            return account.getProtocol(context);
584         }
585        return null;
586    }
587
588    /**
589     * Return the account's protocol
590     * @param context the caller's context
591     * @return the account's protocol (or null if the HostAuth doesn't not exist)
592     */
593    public String getProtocol(Context context) {
594        HostAuth hostAuth = HostAuth.restoreHostAuthWithId(context, mHostAuthKeyRecv);
595        if (hostAuth != null) {
596            return hostAuth.mProtocol;
597        }
598        return null;
599    }
600
601    /**
602     * Return the account ID for a message with a given id
603     *
604     * @param context the caller's context
605     * @param messageId the id of the message
606     * @return the account ID, or -1 if the account doesn't exist
607     */
608    public static long getAccountIdForMessageId(Context context, long messageId) {
609        return Message.getKeyColumnLong(context, messageId, MessageColumns.ACCOUNT_KEY);
610    }
611
612    /**
613     * Return the account for a message with a given id
614     * @param context the caller's context
615     * @param messageId the id of the message
616     * @return the account, or null if the account doesn't exist
617     */
618    public static Account getAccountForMessageId(Context context, long messageId) {
619        long accountId = getAccountIdForMessageId(context, messageId);
620        if (accountId != -1) {
621            return Account.restoreAccountWithId(context, accountId);
622        }
623        return null;
624    }
625
626    /**
627     * @return true if an {@code accountId} is assigned to any existing account.
628     */
629    public static boolean isValidId(Context context, long accountId) {
630        return null != Utility.getFirstRowLong(context, CONTENT_URI, ID_PROJECTION,
631                ID_SELECTION, new String[] {Long.toString(accountId)}, null,
632                ID_PROJECTION_COLUMN);
633    }
634
635    /**
636     * Check a single account for security hold status.
637     */
638    public static boolean isSecurityHold(Context context, long accountId) {
639        return (Utility.getFirstRowLong(context,
640                ContentUris.withAppendedId(Account.CONTENT_URI, accountId),
641                ACCOUNT_FLAGS_PROJECTION, null, null, null, ACCOUNT_FLAGS_COLUMN_FLAGS, 0L)
642                & Account.FLAGS_SECURITY_HOLD) != 0;
643    }
644
645    /**
646     * @return id of the "inbox" mailbox, or -1 if not found.
647     */
648    public static long getInboxId(Context context, long accountId) {
649        return Utility.getFirstRowLong(context, Mailbox.CONTENT_URI, ID_PROJECTION,
650                FIND_INBOX_SELECTION, new String[] {Long.toString(accountId)}, null,
651                ID_PROJECTION_COLUMN, -1L);
652    }
653
654    /**
655     * Clear all account hold flags that are set.
656     *
657     * (This will trigger watchers, and in particular will cause EAS to try and resync the
658     * account(s).)
659     */
660    public static void clearSecurityHoldOnAllAccounts(Context context) {
661        ContentResolver resolver = context.getContentResolver();
662        Cursor c = resolver.query(Account.CONTENT_URI, ACCOUNT_FLAGS_PROJECTION,
663                SECURITY_NONZERO_SELECTION, null, null);
664        try {
665            while (c.moveToNext()) {
666                int flags = c.getInt(ACCOUNT_FLAGS_COLUMN_FLAGS);
667
668                if (0 != (flags & FLAGS_SECURITY_HOLD)) {
669                    ContentValues cv = new ContentValues();
670                    cv.put(AccountColumns.FLAGS, flags & ~FLAGS_SECURITY_HOLD);
671                    long accountId = c.getLong(ACCOUNT_FLAGS_COLUMN_ID);
672                    Uri uri = ContentUris.withAppendedId(Account.CONTENT_URI, accountId);
673                    resolver.update(uri, cv, null, null);
674                }
675            }
676        } finally {
677            c.close();
678        }
679    }
680
681    /**
682     * Override update to enforce a single default account, and do it atomically
683     */
684    @Override
685    public int update(Context context, ContentValues cv) {
686        if (mPolicy != null && mPolicyKey <= 0) {
687            // If a policy is set and there's no policy, link it to the account
688            Policy.setAccountPolicy(context, this, mPolicy, null);
689        }
690        if (cv.containsKey(AccountColumns.IS_DEFAULT) &&
691                cv.getAsBoolean(AccountColumns.IS_DEFAULT)) {
692            ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
693            ContentValues cv1 = new ContentValues();
694            cv1.put(AccountColumns.IS_DEFAULT, false);
695            // Clear the default flag in all accounts
696            ops.add(ContentProviderOperation.newUpdate(CONTENT_URI).withValues(cv1).build());
697            // Update this account
698            ops.add(ContentProviderOperation
699                    .newUpdate(ContentUris.withAppendedId(CONTENT_URI, mId))
700                    .withValues(cv).build());
701            try {
702                context.getContentResolver().applyBatch(AUTHORITY, ops);
703                return 1;
704            } catch (RemoteException e) {
705                // There is nothing to be done here; fail by returning 0
706            } catch (OperationApplicationException e) {
707                // There is nothing to be done here; fail by returning 0
708            }
709            return 0;
710        }
711        return super.update(context, cv);
712    }
713
714    /*
715     * Override this so that we can store the HostAuth's first and link them to the Account
716     * (non-Javadoc)
717     * @see com.android.email.provider.EmailContent#save(android.content.Context)
718     */
719    @Override
720    public Uri save(Context context) {
721        if (isSaved()) {
722            throw new UnsupportedOperationException();
723        }
724        // This logic is in place so I can (a) short circuit the expensive stuff when
725        // possible, and (b) override (and throw) if anyone tries to call save() or update()
726        // directly for Account, which are unsupported.
727        if (mHostAuthRecv == null && mHostAuthSend == null && mIsDefault == false &&
728                mPolicy != null) {
729            return super.save(context);
730        }
731
732        int index = 0;
733        int recvIndex = -1;
734        int sendIndex = -1;
735        int policyIndex = -1;
736
737        // Create operations for saving the send and recv hostAuths
738        // Also, remember which operation in the array they represent
739        ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
740        if (mHostAuthRecv != null) {
741            recvIndex = index++;
742            ops.add(ContentProviderOperation.newInsert(mHostAuthRecv.mBaseUri)
743                    .withValues(mHostAuthRecv.toContentValues())
744                    .build());
745        }
746        if (mHostAuthSend != null) {
747            sendIndex = index++;
748            ops.add(ContentProviderOperation.newInsert(mHostAuthSend.mBaseUri)
749                    .withValues(mHostAuthSend.toContentValues())
750                    .build());
751        }
752        if (mPolicy != null) {
753            policyIndex = index++;
754            ops.add(ContentProviderOperation.newInsert(mPolicy.mBaseUri)
755                    .withValues(mPolicy.toContentValues())
756                    .build());
757        }
758
759        // Create operations for making this the only default account
760        // Note, these are always updates because they change existing accounts
761        if (mIsDefault) {
762            index++;
763            ContentValues cv1 = new ContentValues();
764            cv1.put(AccountColumns.IS_DEFAULT, 0);
765            ops.add(ContentProviderOperation.newUpdate(CONTENT_URI).withValues(cv1).build());
766        }
767
768        // Now do the Account
769        ContentValues cv = null;
770        if (recvIndex >= 0 || sendIndex >= 0 || policyIndex >= 0) {
771            cv = new ContentValues();
772            if (recvIndex >= 0) {
773                cv.put(Account.HOST_AUTH_KEY_RECV, recvIndex);
774            }
775            if (sendIndex >= 0) {
776                cv.put(Account.HOST_AUTH_KEY_SEND, sendIndex);
777            }
778            if (policyIndex >= 0) {
779                cv.put(Account.POLICY_KEY, policyIndex);
780            }
781        }
782
783        ContentProviderOperation.Builder b = ContentProviderOperation.newInsert(mBaseUri);
784        b.withValues(toContentValues());
785        if (cv != null) {
786            b.withValueBackReferences(cv);
787        }
788        ops.add(b.build());
789
790        try {
791            ContentProviderResult[] results =
792                context.getContentResolver().applyBatch(AUTHORITY, ops);
793            // If saving, set the mId's of the various saved objects
794            if (recvIndex >= 0) {
795                long newId = getId(results[recvIndex].uri);
796                mHostAuthKeyRecv = newId;
797                mHostAuthRecv.mId = newId;
798            }
799            if (sendIndex >= 0) {
800                long newId = getId(results[sendIndex].uri);
801                mHostAuthKeySend = newId;
802                mHostAuthSend.mId = newId;
803            }
804            if (policyIndex >= 0) {
805                long newId = getId(results[policyIndex].uri);
806                mPolicyKey = newId;
807                mPolicy.mId = newId;
808            }
809            Uri u = results[index].uri;
810            mId = getId(u);
811            return u;
812        } catch (RemoteException e) {
813            // There is nothing to be done here; fail by returning null
814        } catch (OperationApplicationException e) {
815            // There is nothing to be done here; fail by returning null
816        }
817        return null;
818    }
819
820    @Override
821    public ContentValues toContentValues() {
822        ContentValues values = new ContentValues();
823        values.put(AccountColumns.DISPLAY_NAME, mDisplayName);
824        values.put(AccountColumns.EMAIL_ADDRESS, mEmailAddress);
825        values.put(AccountColumns.SYNC_KEY, mSyncKey);
826        values.put(AccountColumns.SYNC_LOOKBACK, mSyncLookback);
827        values.put(AccountColumns.SYNC_INTERVAL, mSyncInterval);
828        values.put(AccountColumns.HOST_AUTH_KEY_RECV, mHostAuthKeyRecv);
829        values.put(AccountColumns.HOST_AUTH_KEY_SEND, mHostAuthKeySend);
830        values.put(AccountColumns.FLAGS, mFlags);
831        values.put(AccountColumns.IS_DEFAULT, mIsDefault);
832        values.put(AccountColumns.COMPATIBILITY_UUID, mCompatibilityUuid);
833        values.put(AccountColumns.SENDER_NAME, mSenderName);
834        values.put(AccountColumns.RINGTONE_URI, mRingtoneUri);
835        values.put(AccountColumns.PROTOCOL_VERSION, mProtocolVersion);
836        values.put(AccountColumns.NEW_MESSAGE_COUNT, mNewMessageCount);
837        values.put(AccountColumns.SECURITY_SYNC_KEY, mSecuritySyncKey);
838        values.put(AccountColumns.SIGNATURE, mSignature);
839        values.put(AccountColumns.POLICY_KEY, mPolicyKey);
840        values.put(AccountColumns.NOTIFIED_MESSAGE_ID, mNotifiedMessageId);
841        values.put(AccountColumns.NOTIFIED_MESSAGE_COUNT, mNotifiedMessageCount);
842        return values;
843    }
844
845    /**
846     * Supports Parcelable
847     */
848    @Override
849    public int describeContents() {
850        return 0;
851    }
852
853    /**
854     * Supports Parcelable
855     */
856    public static final Parcelable.Creator<Account> CREATOR
857            = new Parcelable.Creator<Account>() {
858        @Override
859        public Account createFromParcel(Parcel in) {
860            return new Account(in);
861        }
862
863        @Override
864        public Account[] newArray(int size) {
865            return new Account[size];
866        }
867    };
868
869    /**
870     * Supports Parcelable
871     */
872    @Override
873    public void writeToParcel(Parcel dest, int flags) {
874        // mBaseUri is not parceled
875        dest.writeLong(mId);
876        dest.writeString(mDisplayName);
877        dest.writeString(mEmailAddress);
878        dest.writeString(mSyncKey);
879        dest.writeInt(mSyncLookback);
880        dest.writeInt(mSyncInterval);
881        dest.writeLong(mHostAuthKeyRecv);
882        dest.writeLong(mHostAuthKeySend);
883        dest.writeInt(mFlags);
884        dest.writeByte(mIsDefault ? (byte)1 : (byte)0);
885        dest.writeString(mCompatibilityUuid);
886        dest.writeString(mSenderName);
887        dest.writeString(mRingtoneUri);
888        dest.writeString(mProtocolVersion);
889        dest.writeInt(mNewMessageCount);
890        dest.writeString(mSecuritySyncKey);
891        dest.writeString(mSignature);
892        dest.writeLong(mPolicyKey);
893        dest.writeLong(mNotifiedMessageId);
894        dest.writeInt(mNotifiedMessageCount);
895
896        if (mHostAuthRecv != null) {
897            dest.writeByte((byte)1);
898            mHostAuthRecv.writeToParcel(dest, flags);
899        } else {
900            dest.writeByte((byte)0);
901        }
902
903        if (mHostAuthSend != null) {
904            dest.writeByte((byte)1);
905            mHostAuthSend.writeToParcel(dest, flags);
906        } else {
907            dest.writeByte((byte)0);
908        }
909    }
910
911    /**
912     * Supports Parcelable
913     */
914    public Account(Parcel in) {
915        mBaseUri = Account.CONTENT_URI;
916        mId = in.readLong();
917        mDisplayName = in.readString();
918        mEmailAddress = in.readString();
919        mSyncKey = in.readString();
920        mSyncLookback = in.readInt();
921        mSyncInterval = in.readInt();
922        mHostAuthKeyRecv = in.readLong();
923        mHostAuthKeySend = in.readLong();
924        mFlags = in.readInt();
925        mIsDefault = in.readByte() == 1;
926        mCompatibilityUuid = in.readString();
927        mSenderName = in.readString();
928        mRingtoneUri = in.readString();
929        mProtocolVersion = in.readString();
930        mNewMessageCount = in.readInt();
931        mSecuritySyncKey = in.readString();
932        mSignature = in.readString();
933        mPolicyKey = in.readLong();
934        mNotifiedMessageId = in.readLong();
935        mNotifiedMessageCount = in.readInt();
936
937        mHostAuthRecv = null;
938        if (in.readByte() == 1) {
939            mHostAuthRecv = new HostAuth(in);
940        }
941
942        mHostAuthSend = null;
943        if (in.readByte() == 1) {
944            mHostAuthSend = new HostAuth(in);
945        }
946    }
947
948    /**
949     * For debugger support only - DO NOT use for code.
950     */
951    @Override
952    public String toString() {
953        StringBuilder sb = new StringBuilder('[');
954        if (mHostAuthRecv != null && mHostAuthRecv.mProtocol != null) {
955            sb.append(mHostAuthRecv.mProtocol);
956            sb.append(':');
957        }
958        if (mDisplayName != null)   sb.append(mDisplayName);
959        sb.append(':');
960        if (mEmailAddress != null)  sb.append(mEmailAddress);
961        sb.append(':');
962        if (mSenderName != null)    sb.append(mSenderName);
963        sb.append(']');
964        return sb.toString();
965    }
966
967}