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