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