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