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