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