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