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