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