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