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