/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.emailcommon.provider; import android.content.ContentProviderOperation; import android.content.ContentProviderResult; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.OperationApplicationException; import android.database.ContentObserver; import android.database.Cursor; import android.media.RingtoneManager; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; import com.android.emailcommon.service.EmailServiceProxy; import com.android.emailcommon.utility.Utility; import com.android.mail.utils.LogUtils; import com.google.common.annotations.VisibleForTesting; import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; public final class Account extends EmailContent implements Parcelable { public static final String TABLE_NAME = "Account"; // Define all pseudo account IDs here to avoid conflict with one another. /** * Pseudo account ID to represent a "combined account" that includes messages and mailboxes * from all defined accounts. * * IMPORTANT: This must never be stored to the database. */ public static final long ACCOUNT_ID_COMBINED_VIEW = 0x1000000000000000L; /** * Pseudo account ID to represent "no account". This may be used any time the account ID * may not be known or when we want to specifically select "no" account. * * IMPORTANT: This must never be stored to the database. */ public static final long NO_ACCOUNT = -1L; /** * Whether or not the user has asked for notifications of new mail in this account * * @deprecated Used only for migration */ @Deprecated public final static int FLAGS_NOTIFY_NEW_MAIL = 1<<0; /** * Whether or not the user has asked for vibration notifications with all new mail * * @deprecated Used only for migration */ @Deprecated public final static int FLAGS_VIBRATE = 1<<1; // Bit mask for the account's deletion policy (see DELETE_POLICY_x below) public static final int FLAGS_DELETE_POLICY_MASK = 1<<2 | 1<<3; public static final int FLAGS_DELETE_POLICY_SHIFT = 2; // Whether the account is in the process of being created; any account reconciliation code // MUST ignore accounts with this bit set; in addition, ContentObservers for this data // SHOULD consider the state of this flag during operation public static final int FLAGS_INCOMPLETE = 1<<4; // Security hold is used when the device is not in compliance with security policies // required by the server; in this state, the user MUST be alerted to the need to update // security settings. Sync adapters SHOULD NOT attempt to sync when this flag is set. public static final int FLAGS_SECURITY_HOLD = 1<<5; // Whether the account supports "smart forward" (i.e. the server appends the original // message along with any attachments to the outgoing message) public static final int FLAGS_SUPPORTS_SMART_FORWARD = 1<<7; // Whether the account should try to cache attachments in the background public static final int FLAGS_BACKGROUND_ATTACHMENTS = 1<<8; // Available to sync adapter public static final int FLAGS_SYNC_ADAPTER = 1<<9; // Sync disabled is a status commanded by the server; the sync adapter SHOULD NOT try to // sync mailboxes in this account automatically. A manual sync request to sync a mailbox // with sync disabled SHOULD try to sync and report any failure result via the UI. public static final int FLAGS_SYNC_DISABLED = 1<<10; // Whether or not server-side search is supported by this account public static final int FLAGS_SUPPORTS_SEARCH = 1<<11; // Whether or not server-side search supports global search (i.e. all mailboxes); only valid // if FLAGS_SUPPORTS_SEARCH is true public static final int FLAGS_SUPPORTS_GLOBAL_SEARCH = 1<<12; // Whether or not the initial folder list has been loaded public static final int FLAGS_INITIAL_FOLDER_LIST_LOADED = 1<<13; // Deletion policy (see FLAGS_DELETE_POLICY_MASK, above) public static final int DELETE_POLICY_NEVER = 0; public static final int DELETE_POLICY_7DAYS = 1<<0; // not supported public static final int DELETE_POLICY_ON_DELETE = 1<<1; // Sentinel values for the mSyncInterval field of both Account records public static final int CHECK_INTERVAL_NEVER = -1; public static final int CHECK_INTERVAL_PUSH = -2; public static final int CHECK_INTERVAL_DEFAULT_PULL = 15; public static Uri CONTENT_URI; public static Uri RESET_NEW_MESSAGE_COUNT_URI; public static Uri NOTIFIER_URI; public static Uri SYNC_SETTING_CHANGED_URI; public static void initAccount() { CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/account"); RESET_NEW_MESSAGE_COUNT_URI = Uri.parse(EmailContent.CONTENT_URI + "/resetNewMessageCount"); NOTIFIER_URI = Uri.parse(EmailContent.CONTENT_NOTIFIER_URI + "/account"); SYNC_SETTING_CHANGED_URI = Uri.parse( EmailContent.CONTENT_SYNC_SETTING_CHANGED_URI + "/account"); } public String mDisplayName; public String mEmailAddress; public String mSyncKey; public int mSyncLookback; private int mSyncInterval; public long mHostAuthKeyRecv; public long mHostAuthKeySend; public int mFlags; public String mSenderName; /** @deprecated Used only for migration */ @Deprecated private String mRingtoneUri; public String mProtocolVersion; public String mSecuritySyncKey; public String mSignature; public long mPolicyKey; public long mPingDuration; public int mCapabilities; @VisibleForTesting static final String JSON_TAG_HOST_AUTH_RECV = "hostAuthRecv"; @VisibleForTesting static final String JSON_TAG_HOST_AUTH_SEND = "hostAuthSend"; // Convenience for creating/working with an account public transient HostAuth mHostAuthRecv; public transient HostAuth mHostAuthSend; public transient Policy mPolicy; // Marks this account as being a temporary entry, so we know to use it directly and not go // through the database or any caches private transient boolean mTemporary; public static final int CONTENT_ID_COLUMN = 0; public static final int CONTENT_DISPLAY_NAME_COLUMN = 1; public static final int CONTENT_EMAIL_ADDRESS_COLUMN = 2; public static final int CONTENT_SYNC_KEY_COLUMN = 3; public static final int CONTENT_SYNC_LOOKBACK_COLUMN = 4; public static final int CONTENT_SYNC_INTERVAL_COLUMN = 5; public static final int CONTENT_HOST_AUTH_KEY_RECV_COLUMN = 6; public static final int CONTENT_HOST_AUTH_KEY_SEND_COLUMN = 7; public static final int CONTENT_FLAGS_COLUMN = 8; public static final int CONTENT_SENDER_NAME_COLUMN = 9; public static final int CONTENT_RINGTONE_URI_COLUMN = 10; public static final int CONTENT_PROTOCOL_VERSION_COLUMN = 11; public static final int CONTENT_SECURITY_SYNC_KEY_COLUMN = 12; public static final int CONTENT_SIGNATURE_COLUMN = 13; public static final int CONTENT_POLICY_KEY_COLUMN = 14; public static final int CONTENT_PING_DURATION_COLUMN = 15; public static final int CONTENT_MAX_ATTACHMENT_SIZE_COLUMN = 16; public static final int CONTENT_CAPABILITIES_COLUMN = 17; public static final String[] CONTENT_PROJECTION = { AttachmentColumns._ID, AccountColumns.DISPLAY_NAME, AccountColumns.EMAIL_ADDRESS, AccountColumns.SYNC_KEY, AccountColumns.SYNC_LOOKBACK, AccountColumns.SYNC_INTERVAL, AccountColumns.HOST_AUTH_KEY_RECV, AccountColumns.HOST_AUTH_KEY_SEND, AccountColumns.FLAGS, AccountColumns.SENDER_NAME, AccountColumns.RINGTONE_URI, AccountColumns.PROTOCOL_VERSION, AccountColumns.SECURITY_SYNC_KEY, AccountColumns.SIGNATURE, AccountColumns.POLICY_KEY, AccountColumns.PING_DURATION, AccountColumns.MAX_ATTACHMENT_SIZE, AccountColumns.CAPABILITIES }; public static final int ACCOUNT_FLAGS_COLUMN_ID = 0; public static final int ACCOUNT_FLAGS_COLUMN_FLAGS = 1; public static final String[] ACCOUNT_FLAGS_PROJECTION = { AccountColumns._ID, AccountColumns.FLAGS}; public static final String SECURITY_NONZERO_SELECTION = AccountColumns.POLICY_KEY + " IS NOT NULL AND " + AccountColumns.POLICY_KEY + "!=0"; private static final String FIND_INBOX_SELECTION = MailboxColumns.TYPE + " = " + Mailbox.TYPE_INBOX + " AND " + MailboxColumns.ACCOUNT_KEY + " =?"; public Account() { mBaseUri = CONTENT_URI; // other defaults (policy) mRingtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION).toString(); mSyncInterval = -1; mSyncLookback = -1; mFlags = 0; } public static Account restoreAccountWithId(Context context, long id) { return restoreAccountWithId(context, id, null); } public static Account restoreAccountWithId(Context context, long id, ContentObserver observer) { return EmailContent.restoreContentWithId(context, Account.class, Account.CONTENT_URI, Account.CONTENT_PROJECTION, id, observer); } public static Account restoreAccountWithAddress(Context context, String emailAddress) { return restoreAccountWithAddress(context, emailAddress, null); } public static Account restoreAccountWithAddress(Context context, String emailAddress, ContentObserver observer) { final Cursor c = context.getContentResolver().query(CONTENT_URI, new String[] {AccountColumns._ID}, AccountColumns.EMAIL_ADDRESS + "=?", new String[] {emailAddress}, null); try { if (c == null || !c.moveToFirst()) { return null; } final long id = c.getLong(c.getColumnIndex(AccountColumns._ID)); return restoreAccountWithId(context, id, observer); } finally { if (c != null) { c.close(); } } } @Override protected Uri getContentNotificationUri() { return Account.CONTENT_URI; } /** * Refresh an account that has already been loaded. This is slightly less expensive * that generating a brand-new account object. */ public void refresh(Context context) { Cursor c = context.getContentResolver().query(getUri(), Account.CONTENT_PROJECTION, null, null, null); try { c.moveToFirst(); restore(c); } finally { if (c != null) { c.close(); } } } @Override public void restore(Cursor cursor) { mId = cursor.getLong(CONTENT_ID_COLUMN); mBaseUri = CONTENT_URI; mDisplayName = cursor.getString(CONTENT_DISPLAY_NAME_COLUMN); mEmailAddress = cursor.getString(CONTENT_EMAIL_ADDRESS_COLUMN); mSyncKey = cursor.getString(CONTENT_SYNC_KEY_COLUMN); mSyncLookback = cursor.getInt(CONTENT_SYNC_LOOKBACK_COLUMN); mSyncInterval = cursor.getInt(CONTENT_SYNC_INTERVAL_COLUMN); mHostAuthKeyRecv = cursor.getLong(CONTENT_HOST_AUTH_KEY_RECV_COLUMN); mHostAuthKeySend = cursor.getLong(CONTENT_HOST_AUTH_KEY_SEND_COLUMN); mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN); mSenderName = cursor.getString(CONTENT_SENDER_NAME_COLUMN); mRingtoneUri = cursor.getString(CONTENT_RINGTONE_URI_COLUMN); mProtocolVersion = cursor.getString(CONTENT_PROTOCOL_VERSION_COLUMN); mSecuritySyncKey = cursor.getString(CONTENT_SECURITY_SYNC_KEY_COLUMN); mSignature = cursor.getString(CONTENT_SIGNATURE_COLUMN); mPolicyKey = cursor.getLong(CONTENT_POLICY_KEY_COLUMN); mPingDuration = cursor.getLong(CONTENT_PING_DURATION_COLUMN); mCapabilities = cursor.getInt(CONTENT_CAPABILITIES_COLUMN); } public boolean isTemporary() { return mTemporary; } public void setTemporary(boolean temporary) { mTemporary = temporary; } private static long getId(Uri u) { return Long.parseLong(u.getPathSegments().get(1)); } public long getId() { return mId; } /** * Returns the user-visible name for the account, eg. "My work address" * or "foo@exemple.com". * @return the user-visible name for the account. */ public String getDisplayName() { return mDisplayName; } /** * Set the description. Be sure to call save() to commit to database. * @param description the new description */ public void setDisplayName(String description) { mDisplayName = description; } /** * @return the email address for this account */ public String getEmailAddress() { return mEmailAddress; } /** * Set the Email address for this account. Be sure to call save() to commit to database. * @param emailAddress the new email address for this account */ public void setEmailAddress(String emailAddress) { mEmailAddress = emailAddress; } /** * @return the sender's name for this account */ public String getSenderName() { return mSenderName; } /** * Set the sender's name. Be sure to call save() to commit to database. * @param name the new sender name */ public void setSenderName(String name) { mSenderName = name; } public String getSignature() { return mSignature; } @VisibleForTesting public void setSignature(String signature) { mSignature = signature; } /** * @return the minutes per check (for polling) * TODO define sentinel values for "never", "push", etc. See Account.java */ public int getSyncInterval() { // Fixed unsynced value and account capability. Change to default pull value if (!hasCapability(EmailServiceProxy.CAPABILITY_PUSH) && mSyncInterval == CHECK_INTERVAL_PUSH) { return CHECK_INTERVAL_DEFAULT_PULL; } return mSyncInterval; } /** * Set the minutes per check (for polling). Be sure to call save() to commit to database. * TODO define sentinel values for "never", "push", etc. See Account.java * @param minutes the number of minutes between polling checks */ public void setSyncInterval(int minutes) { // Fixed unsynced value and account capability. Change to default pull value if (!hasCapability(EmailServiceProxy.CAPABILITY_PUSH) && mSyncInterval == CHECK_INTERVAL_PUSH) { mSyncInterval = CHECK_INTERVAL_DEFAULT_PULL; } else { mSyncInterval = minutes; } } /** * @return One of the {@code Account.SYNC_WINDOW_*} constants that represents the sync * lookback window. * TODO define sentinel values for "all", "1 month", etc. See Account.java */ public int getSyncLookback() { return mSyncLookback; } /** * Set the sync lookback window. Be sure to call save() to commit to database. * TODO define sentinel values for "all", "1 month", etc. See Account.java * @param value One of the {@link com.android.emailcommon.service.SyncWindow} constants */ public void setSyncLookback(int value) { mSyncLookback = value; } /** * @return the current ping duration. */ public long getPingDuration() { return mPingDuration; } /** * Set the ping duration. Be sure to call save() to commit to database. */ public void setPingDuration(long value) { mPingDuration = value; } /** * @return the current account capabilities. */ public int getCapabilities() { return mCapabilities; } /** * Set the account capabilities. Be sure to call save() to commit to database. */ public void setCapabilities(int value) { mCapabilities = value; } /** * @return the flags for this account */ public int getFlags() { return mFlags; } /** * Set the flags for this account * @param newFlags the new value for the flags */ public void setFlags(int newFlags) { mFlags = newFlags; } /** * @return the ringtone Uri for this account * @deprecated Used only for migration */ @Deprecated public String getRingtone() { return mRingtoneUri; } /** * Set the "delete policy" as a simple 0,1,2 value set. * @param newPolicy the new delete policy */ public void setDeletePolicy(int newPolicy) { mFlags &= ~FLAGS_DELETE_POLICY_MASK; mFlags |= (newPolicy << FLAGS_DELETE_POLICY_SHIFT) & FLAGS_DELETE_POLICY_MASK; } /** * Return the "delete policy" as a simple 0,1,2 value set. * @return the current delete policy */ public int getDeletePolicy() { return (mFlags & FLAGS_DELETE_POLICY_MASK) >> FLAGS_DELETE_POLICY_SHIFT; } public HostAuth getOrCreateHostAuthSend(Context context) { if (mHostAuthSend == null) { if (mHostAuthKeySend != 0) { mHostAuthSend = HostAuth.restoreHostAuthWithId(context, mHostAuthKeySend); } else { mHostAuthSend = new HostAuth(); } } return mHostAuthSend; } public HostAuth getOrCreateHostAuthRecv(Context context) { if (mHostAuthRecv == null) { if (mHostAuthKeyRecv != 0) { mHostAuthRecv = HostAuth.restoreHostAuthWithId(context, mHostAuthKeyRecv); } else { mHostAuthRecv = new HostAuth(); } } return mHostAuthRecv; } /** * Return the id of the default account. If one hasn't been explicitly specified, return the * first one in the database. If no account exists, returns {@link #NO_ACCOUNT}. * * @param context the caller's context * @param lastUsedAccountId the last used account id, which is the basis of the default account */ public static long getDefaultAccountId(final Context context, final long lastUsedAccountId) { final Cursor cursor = context.getContentResolver().query( CONTENT_URI, ID_PROJECTION, null, null, null); long firstAccount = NO_ACCOUNT; try { if (cursor != null && cursor.moveToFirst()) { do { final long accountId = cursor.getLong(Account.ID_PROJECTION_COLUMN); if (accountId == lastUsedAccountId) { return accountId; } if (firstAccount == NO_ACCOUNT) { firstAccount = accountId; } } while (cursor.moveToNext()); } } finally { if (cursor != null) { cursor.close(); } } return firstAccount; } /** * Given an account id, return the account's protocol * @param context the caller's context * @param accountId the id of the account to be examined * @return the account's protocol (or null if the Account or HostAuth do not exist) */ public static String getProtocol(Context context, long accountId) { Account account = Account.restoreAccountWithId(context, accountId); if (account != null) { return account.getProtocol(context); } return null; } /** * Return the account's protocol * @param context the caller's context * @return the account's protocol (or null if the HostAuth doesn't not exist) */ public String getProtocol(Context context) { HostAuth hostAuth = getOrCreateHostAuthRecv(context); if (hostAuth != null) { return hostAuth.mProtocol; } return null; } /** * Return a corresponding account manager object using the passed in type * * @param type We can't look up the account type from here, so pass it in * @return system account object */ public android.accounts.Account getAccountManagerAccount(String type) { return new android.accounts.Account(mEmailAddress, type); } /** * Return the account ID for a message with a given id * * @param context the caller's context * @param messageId the id of the message * @return the account ID, or -1 if the account doesn't exist */ public static long getAccountIdForMessageId(Context context, long messageId) { return Message.getKeyColumnLong(context, messageId, MessageColumns.ACCOUNT_KEY); } /** * Return the account for a message with a given id * @param context the caller's context * @param messageId the id of the message * @return the account, or null if the account doesn't exist */ public static Account getAccountForMessageId(Context context, long messageId) { long accountId = getAccountIdForMessageId(context, messageId); if (accountId != -1) { return Account.restoreAccountWithId(context, accountId); } return null; } /** * @return true if an {@code accountId} is assigned to any existing account. */ public static boolean isValidId(Context context, long accountId) { return null != Utility.getFirstRowLong(context, CONTENT_URI, ID_PROJECTION, ID_SELECTION, new String[] {Long.toString(accountId)}, null, ID_PROJECTION_COLUMN); } /** * Check a single account for security hold status. */ public static boolean isSecurityHold(Context context, long accountId) { return (Utility.getFirstRowLong(context, ContentUris.withAppendedId(Account.CONTENT_URI, accountId), ACCOUNT_FLAGS_PROJECTION, null, null, null, ACCOUNT_FLAGS_COLUMN_FLAGS, 0L) & Account.FLAGS_SECURITY_HOLD) != 0; } /** * @return id of the "inbox" mailbox, or -1 if not found. */ public static long getInboxId(Context context, long accountId) { return Utility.getFirstRowLong(context, Mailbox.CONTENT_URI, ID_PROJECTION, FIND_INBOX_SELECTION, new String[] {Long.toString(accountId)}, null, ID_PROJECTION_COLUMN, -1L); } /** * Clear all account hold flags that are set. * * (This will trigger watchers, and in particular will cause EAS to try and resync the * account(s).) */ public static void clearSecurityHoldOnAllAccounts(Context context) { ContentResolver resolver = context.getContentResolver(); Cursor c = resolver.query(Account.CONTENT_URI, ACCOUNT_FLAGS_PROJECTION, SECURITY_NONZERO_SELECTION, null, null); try { while (c.moveToNext()) { int flags = c.getInt(ACCOUNT_FLAGS_COLUMN_FLAGS); if (0 != (flags & FLAGS_SECURITY_HOLD)) { ContentValues cv = new ContentValues(); cv.put(AccountColumns.FLAGS, flags & ~FLAGS_SECURITY_HOLD); long accountId = c.getLong(ACCOUNT_FLAGS_COLUMN_ID); Uri uri = ContentUris.withAppendedId(Account.CONTENT_URI, accountId); resolver.update(uri, cv, null, null); } } } finally { c.close(); } } /* * Override this so that we can store the HostAuth's first and link them to the Account * (non-Javadoc) * @see com.android.email.provider.EmailContent#save(android.content.Context) */ @Override public Uri save(Context context) { if (isSaved()) { throw new UnsupportedOperationException(); } // This logic is in place so I can (a) short circuit the expensive stuff when // possible, and (b) override (and throw) if anyone tries to call save() or update() // directly for Account, which are unsupported. if (mHostAuthRecv == null && mHostAuthSend == null && mPolicy != null) { return super.save(context); } int index = 0; int recvIndex = -1; int recvCredentialsIndex = -1; int sendIndex = -1; int sendCredentialsIndex = -1; // Create operations for saving the send and recv hostAuths, and their credentials. // Also, remember which operation in the array they represent ArrayList ops = new ArrayList(); if (mHostAuthRecv != null) { if (mHostAuthRecv.mCredential != null) { recvCredentialsIndex = index++; ops.add(ContentProviderOperation.newInsert(mHostAuthRecv.mCredential.mBaseUri) .withValues(mHostAuthRecv.mCredential.toContentValues()) .build()); } recvIndex = index++; final ContentProviderOperation.Builder b = ContentProviderOperation.newInsert( mHostAuthRecv.mBaseUri); b.withValues(mHostAuthRecv.toContentValues()); if (recvCredentialsIndex >= 0) { final ContentValues cv = new ContentValues(); cv.put(HostAuthColumns.CREDENTIAL_KEY, recvCredentialsIndex); b.withValueBackReferences(cv); } ops.add(b.build()); } if (mHostAuthSend != null) { if (mHostAuthSend.mCredential != null) { if (mHostAuthRecv.mCredential != null && mHostAuthRecv.mCredential.equals(mHostAuthSend.mCredential)) { // These two credentials are identical, use the same row. sendCredentialsIndex = recvCredentialsIndex; } else { sendCredentialsIndex = index++; ops.add(ContentProviderOperation.newInsert(mHostAuthSend.mCredential.mBaseUri) .withValues(mHostAuthSend.mCredential.toContentValues()) .build()); } } sendIndex = index++; final ContentProviderOperation.Builder b = ContentProviderOperation.newInsert( mHostAuthSend.mBaseUri); b.withValues(mHostAuthSend.toContentValues()); if (sendCredentialsIndex >= 0) { final ContentValues cv = new ContentValues(); cv.put(HostAuthColumns.CREDENTIAL_KEY, sendCredentialsIndex); b.withValueBackReferences(cv); } ops.add(b.build()); } // Now do the Account ContentValues cv = null; if (recvIndex >= 0 || sendIndex >= 0) { cv = new ContentValues(); if (recvIndex >= 0) { cv.put(AccountColumns.HOST_AUTH_KEY_RECV, recvIndex); } if (sendIndex >= 0) { cv.put(AccountColumns.HOST_AUTH_KEY_SEND, sendIndex); } } ContentProviderOperation.Builder b = ContentProviderOperation.newInsert(mBaseUri); b.withValues(toContentValues()); if (cv != null) { b.withValueBackReferences(cv); } ops.add(b.build()); try { ContentProviderResult[] results = context.getContentResolver().applyBatch(EmailContent.AUTHORITY, ops); // If saving, set the mId's of the various saved objects if (recvIndex >= 0) { long newId = getId(results[recvIndex].uri); mHostAuthKeyRecv = newId; mHostAuthRecv.mId = newId; } if (sendIndex >= 0) { long newId = getId(results[sendIndex].uri); mHostAuthKeySend = newId; mHostAuthSend.mId = newId; } Uri u = results[index].uri; mId = getId(u); return u; } catch (RemoteException e) { // There is nothing to be done here; fail by returning null } catch (OperationApplicationException e) { // There is nothing to be done here; fail by returning null } return null; } @Override public ContentValues toContentValues() { ContentValues values = new ContentValues(); values.put(AccountColumns.DISPLAY_NAME, mDisplayName); values.put(AccountColumns.EMAIL_ADDRESS, mEmailAddress); values.put(AccountColumns.SYNC_KEY, mSyncKey); values.put(AccountColumns.SYNC_LOOKBACK, mSyncLookback); values.put(AccountColumns.SYNC_INTERVAL, mSyncInterval); values.put(AccountColumns.HOST_AUTH_KEY_RECV, mHostAuthKeyRecv); values.put(AccountColumns.HOST_AUTH_KEY_SEND, mHostAuthKeySend); values.put(AccountColumns.FLAGS, mFlags); values.put(AccountColumns.SENDER_NAME, mSenderName); values.put(AccountColumns.RINGTONE_URI, mRingtoneUri); values.put(AccountColumns.PROTOCOL_VERSION, mProtocolVersion); values.put(AccountColumns.SECURITY_SYNC_KEY, mSecuritySyncKey); values.put(AccountColumns.SIGNATURE, mSignature); values.put(AccountColumns.POLICY_KEY, mPolicyKey); values.put(AccountColumns.PING_DURATION, mPingDuration); values.put(AccountColumns.CAPABILITIES, mCapabilities); return values; } public String toJsonString(final Context context) { ensureLoaded(context); final JSONObject json = toJson(); if (json != null) { return json.toString(); } return null; } protected JSONObject toJson() { try { final JSONObject json = new JSONObject(); json.putOpt(AccountColumns.DISPLAY_NAME, mDisplayName); json.put(AccountColumns.EMAIL_ADDRESS, mEmailAddress); json.put(AccountColumns.SYNC_LOOKBACK, mSyncLookback); json.put(AccountColumns.SYNC_INTERVAL, mSyncInterval); final JSONObject recvJson = mHostAuthRecv.toJson(); json.put(JSON_TAG_HOST_AUTH_RECV, recvJson); if (mHostAuthSend != null) { final JSONObject sendJson = mHostAuthSend.toJson(); json.put(JSON_TAG_HOST_AUTH_SEND, sendJson); } json.put(AccountColumns.FLAGS, mFlags); json.putOpt(AccountColumns.SENDER_NAME, mSenderName); json.putOpt(AccountColumns.PROTOCOL_VERSION, mProtocolVersion); json.putOpt(AccountColumns.SIGNATURE, mSignature); json.put(AccountColumns.PING_DURATION, mPingDuration); json.put(AccountColumns.CAPABILITIES, mCapabilities); return json; } catch (final JSONException e) { LogUtils.d(LogUtils.TAG, e, "Exception while serializing Account"); } return null; } public static Account fromJsonString(final String jsonString) { try { final JSONObject json = new JSONObject(jsonString); return fromJson(json); } catch (final JSONException e) { LogUtils.d(LogUtils.TAG, e, "Could not parse json for account"); } return null; } protected static Account fromJson(final JSONObject json) { try { final Account a = new Account(); a.mDisplayName = json.optString(AccountColumns.DISPLAY_NAME); a.mEmailAddress = json.getString(AccountColumns.EMAIL_ADDRESS); // SYNC_KEY is not stored a.mSyncLookback = json.getInt(AccountColumns.SYNC_LOOKBACK); a.mSyncInterval = json.getInt(AccountColumns.SYNC_INTERVAL); final JSONObject recvJson = json.getJSONObject(JSON_TAG_HOST_AUTH_RECV); a.mHostAuthRecv = HostAuth.fromJson(recvJson); final JSONObject sendJson = json.optJSONObject(JSON_TAG_HOST_AUTH_SEND); if (sendJson != null) { a.mHostAuthSend = HostAuth.fromJson(sendJson); } a.mFlags = json.getInt(AccountColumns.FLAGS); a.mSenderName = json.optString(AccountColumns.SENDER_NAME); a.mProtocolVersion = json.optString(AccountColumns.PROTOCOL_VERSION); // SECURITY_SYNC_KEY is not stored a.mSignature = json.optString(AccountColumns.SIGNATURE); // POLICY_KEY is not stored a.mPingDuration = json.optInt(AccountColumns.PING_DURATION, 0); a.mCapabilities = json.optInt(AccountColumns.CAPABILITIES, 0); return a; } catch (final JSONException e) { LogUtils.d(LogUtils.TAG, e, "Exception while deserializing Account"); } return null; } /** * Ensure that all optionally-loaded fields are populated from the provider. * @param context for provider loads */ public void ensureLoaded(final Context context) { if (mHostAuthKeyRecv == 0 && mHostAuthRecv == null) { throw new IllegalStateException("Trying to load incomplete Account object"); } getOrCreateHostAuthRecv(context).ensureLoaded(context); if (mHostAuthKeySend != 0) { getOrCreateHostAuthSend(context); if (mHostAuthSend != null) { mHostAuthSend.ensureLoaded(context); } } } /** * Returns whether or not the capability is supported by the account. * @see EmailServiceProxy#CAPABILITY_* */ public boolean hasCapability(int capability) { return (mCapabilities & capability) != 0; } /** * Supports Parcelable */ @Override public int describeContents() { return 0; } /** * Supports Parcelable */ public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public Account createFromParcel(Parcel in) { return new Account(in); } @Override public Account[] newArray(int size) { return new Account[size]; } }; /** * Supports Parcelable */ @Override public void writeToParcel(Parcel dest, int flags) { // mBaseUri is not parceled dest.writeLong(mId); dest.writeString(mDisplayName); dest.writeString(mEmailAddress); dest.writeString(mSyncKey); dest.writeInt(mSyncLookback); dest.writeInt(mSyncInterval); dest.writeLong(mHostAuthKeyRecv); dest.writeLong(mHostAuthKeySend); dest.writeInt(mFlags); dest.writeString("" /* mCompatibilityUuid */); dest.writeString(mSenderName); dest.writeString(mRingtoneUri); dest.writeString(mProtocolVersion); dest.writeInt(0 /* mNewMessageCount */); dest.writeString(mSecuritySyncKey); dest.writeString(mSignature); dest.writeLong(mPolicyKey); if (mHostAuthRecv != null) { dest.writeByte((byte)1); mHostAuthRecv.writeToParcel(dest, flags); } else { dest.writeByte((byte)0); } if (mHostAuthSend != null) { dest.writeByte((byte)1); mHostAuthSend.writeToParcel(dest, flags); } else { dest.writeByte((byte)0); } dest.writeInt(mCapabilities); } /** * Supports Parcelable */ public Account(Parcel in) { mBaseUri = Account.CONTENT_URI; mId = in.readLong(); mDisplayName = in.readString(); mEmailAddress = in.readString(); mSyncKey = in.readString(); mSyncLookback = in.readInt(); mSyncInterval = in.readInt(); mHostAuthKeyRecv = in.readLong(); mHostAuthKeySend = in.readLong(); mFlags = in.readInt(); /* mCompatibilityUuid = */ in.readString(); mSenderName = in.readString(); mRingtoneUri = in.readString(); mProtocolVersion = in.readString(); /* mNewMessageCount = */ in.readInt(); mSecuritySyncKey = in.readString(); mSignature = in.readString(); mPolicyKey = in.readLong(); mHostAuthRecv = null; if (in.readByte() == 1) { mHostAuthRecv = new HostAuth(in); } mHostAuthSend = null; if (in.readByte() == 1) { mHostAuthSend = new HostAuth(in); } mCapabilities = in.readInt(); } /** * For debugger support only - DO NOT use for code. */ @Override public String toString() { StringBuilder sb = new StringBuilder("["); if (mHostAuthRecv != null && mHostAuthRecv.mProtocol != null) { sb.append(mHostAuthRecv.mProtocol); sb.append(':'); } if (mDisplayName != null) sb.append(mDisplayName); sb.append(':'); if (mEmailAddress != null) sb.append(mEmailAddress); sb.append(':'); if (mSenderName != null) sb.append(mSenderName); sb.append(']'); return sb.toString(); } }