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