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