/* * Copyright (C) 2009 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.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.OperationApplicationException; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; import android.provider.CalendarContract; import android.provider.ContactsContract; import android.text.TextUtils; import android.util.SparseBooleanArray; import com.android.emailcommon.Logging; import com.android.emailcommon.R; import com.android.emailcommon.utility.Utility; import com.android.mail.utils.LogUtils; import java.util.ArrayList; public class Mailbox extends EmailContent implements EmailContent.MailboxColumns, Parcelable { /** * Sync extras key when syncing one or more mailboxes to specify how many * mailboxes are included in the extra. */ public static final String SYNC_EXTRA_MAILBOX_COUNT = "__mailboxCount__"; /** * Sync extras key pattern when syncing one or more mailboxes to specify * which mailbox to sync. Is intentionally private, we have helper functions * to set up an appropriate bundle, or read its contents. */ private static final String SYNC_EXTRA_MAILBOX_ID_PATTERN = "__mailboxId%d__"; /** * Sync extra key indicating that we are doing a sync of the folder structure for an account. */ public static final String SYNC_EXTRA_ACCOUNT_ONLY = "__account_only__"; /** * Sync extra key indicating that we are only starting a ping. */ public static final String SYNC_EXTRA_PUSH_ONLY = "__push_only__"; /** * Sync extras key to specify that only a specific mailbox type should be synced. */ public static final String SYNC_EXTRA_MAILBOX_TYPE = "__mailboxType__"; /** * Sync extras key when syncing a mailbox to specify how many additional messages to sync. */ public static final String SYNC_EXTRA_DELTA_MESSAGE_COUNT = "__deltaMessageCount__"; public static final String SYNC_EXTRA_NOOP = "__noop__"; public static final String TABLE_NAME = "Mailbox"; public static Uri CONTENT_URI; public static Uri MESSAGE_COUNT_URI; public static Uri SYNC_SETTING_CHANGED_URI; public static void initMailbox() { CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/mailbox"); MESSAGE_COUNT_URI = Uri.parse(EmailContent.CONTENT_URI + "/mailboxCount"); SYNC_SETTING_CHANGED_URI = Uri.parse( EmailContent.CONTENT_SYNC_SETTING_CHANGED_URI + "/mailbox"); } private static String formatMailboxIdExtra(final int index) { return String.format(SYNC_EXTRA_MAILBOX_ID_PATTERN, index); } public static Bundle createSyncBundle(final ArrayList mailboxIds) { final Bundle bundle = new Bundle(mailboxIds.size() + 1); bundle.putInt(SYNC_EXTRA_MAILBOX_COUNT, mailboxIds.size()); for (int i = 0; i < mailboxIds.size(); i++) { bundle.putLong(formatMailboxIdExtra(i), mailboxIds.get(i)); } return bundle; } public static Bundle createSyncBundle(final long[] mailboxIds) { final Bundle bundle = new Bundle(mailboxIds.length + 1); bundle.putInt(SYNC_EXTRA_MAILBOX_COUNT, mailboxIds.length); for (int i = 0; i < mailboxIds.length; i++) { bundle.putLong(formatMailboxIdExtra(i), mailboxIds[i]); } return bundle; } public static Bundle createSyncBundle(final long mailboxId) { final Bundle bundle = new Bundle(2); bundle.putInt(SYNC_EXTRA_MAILBOX_COUNT, 1); bundle.putLong(formatMailboxIdExtra(0), mailboxId); return bundle; } public static long[] getMailboxIdsFromBundle(Bundle bundle) { final int count = bundle.getInt(SYNC_EXTRA_MAILBOX_COUNT, 0); if (count > 0) { if (bundle.getBoolean(SYNC_EXTRA_PUSH_ONLY, false)) { LogUtils.w(Logging.LOG_TAG, "Mailboxes specified in a push only sync"); } if (bundle.getBoolean(SYNC_EXTRA_ACCOUNT_ONLY, false)) { LogUtils.w(Logging.LOG_TAG, "Mailboxes specified in an account only sync"); } final long [] result = new long[count]; for (int i = 0; i < count; i++) { result[i] = bundle.getLong(formatMailboxIdExtra(i), 0); } return result; } else { return null; } } public static boolean isAccountOnlyExtras(Bundle bundle) { final boolean result = bundle.getBoolean(SYNC_EXTRA_ACCOUNT_ONLY, false); if (result) { final int count = bundle.getInt(SYNC_EXTRA_MAILBOX_COUNT, 0); if (count != 0) { LogUtils.w(Logging.LOG_TAG, "Mailboxes specified in an account only sync"); } } return result; } public static boolean isPushOnlyExtras(Bundle bundle) { final boolean result = bundle.getBoolean(SYNC_EXTRA_PUSH_ONLY, false); if (result) { final int count = bundle.getInt(SYNC_EXTRA_MAILBOX_COUNT, 0); if (count != 0) { LogUtils.w(Logging.LOG_TAG, "Mailboxes specified in a push only sync"); } } return result; } public String mDisplayName; public String mServerId; public String mParentServerId; public long mParentKey; public long mAccountKey; public int mType; public int mDelimiter; public String mSyncKey; public int mSyncLookback; public int mSyncInterval; public long mSyncTime; public boolean mFlagVisible = true; public int mFlags; public String mSyncStatus; public long mLastTouchedTime; public int mUiSyncStatus; public int mUiLastSyncResult; public int mTotalCount; public String mHierarchicalName; public long mLastFullSyncTime; public static final int CONTENT_ID_COLUMN = 0; public static final int CONTENT_DISPLAY_NAME_COLUMN = 1; public static final int CONTENT_SERVER_ID_COLUMN = 2; public static final int CONTENT_PARENT_SERVER_ID_COLUMN = 3; public static final int CONTENT_ACCOUNT_KEY_COLUMN = 4; public static final int CONTENT_TYPE_COLUMN = 5; public static final int CONTENT_DELIMITER_COLUMN = 6; public static final int CONTENT_SYNC_KEY_COLUMN = 7; public static final int CONTENT_SYNC_LOOKBACK_COLUMN = 8; public static final int CONTENT_SYNC_INTERVAL_COLUMN = 9; public static final int CONTENT_SYNC_TIME_COLUMN = 10; public static final int CONTENT_FLAG_VISIBLE_COLUMN = 11; public static final int CONTENT_FLAGS_COLUMN = 12; public static final int CONTENT_SYNC_STATUS_COLUMN = 13; public static final int CONTENT_PARENT_KEY_COLUMN = 14; public static final int CONTENT_LAST_TOUCHED_TIME_COLUMN = 15; public static final int CONTENT_UI_SYNC_STATUS_COLUMN = 16; public static final int CONTENT_UI_LAST_SYNC_RESULT_COLUMN = 17; public static final int CONTENT_TOTAL_COUNT_COLUMN = 18; public static final int CONTENT_HIERARCHICAL_NAME_COLUMN = 19; public static final int CONTENT_LAST_FULL_SYNC_COLUMN = 20; /** * NOTE: If fields are added or removed, the method {@link #getHashes()} * MUST be updated. */ public static final String[] CONTENT_PROJECTION = new String[] { MailboxColumns._ID, MailboxColumns.DISPLAY_NAME, MailboxColumns.SERVER_ID, MailboxColumns.PARENT_SERVER_ID, MailboxColumns.ACCOUNT_KEY, MailboxColumns.TYPE, MailboxColumns.DELIMITER, MailboxColumns.SYNC_KEY, MailboxColumns.SYNC_LOOKBACK, MailboxColumns.SYNC_INTERVAL, MailboxColumns.SYNC_TIME, MailboxColumns.FLAG_VISIBLE, MailboxColumns.FLAGS, MailboxColumns.SYNC_STATUS, MailboxColumns.PARENT_KEY, MailboxColumns.LAST_TOUCHED_TIME, MailboxColumns.UI_SYNC_STATUS, MailboxColumns.UI_LAST_SYNC_RESULT, MailboxColumns.TOTAL_COUNT, MailboxColumns.HIERARCHICAL_NAME, MailboxColumns.LAST_FULL_SYNC_TIME }; /** Selection by server pathname for a given account */ public static final String PATH_AND_ACCOUNT_SELECTION = MailboxColumns.SERVER_ID + "=? and " + MailboxColumns.ACCOUNT_KEY + "=?"; private static final String[] MAILBOX_TYPE_PROJECTION = new String [] { MailboxColumns.TYPE }; private static final int MAILBOX_TYPE_TYPE_COLUMN = 0; private static final String[] MAILBOX_DISPLAY_NAME_PROJECTION = new String [] { MailboxColumns.DISPLAY_NAME }; private static final int MAILBOX_DISPLAY_NAME_COLUMN = 0; /** * Projection to use when reading {@link MailboxColumns#ACCOUNT_KEY} for a mailbox. */ private static final String[] ACCOUNT_KEY_PROJECTION = { MailboxColumns.ACCOUNT_KEY }; private static final int ACCOUNT_KEY_PROJECTION_ACCOUNT_KEY_COLUMN = 0; /** * Projection for querying data needed during a sync. */ public interface ProjectionSyncData { public static final int COLUMN_SERVER_ID = 0; public static final int COLUMN_SYNC_KEY = 1; public static final String[] PROJECTION = { MailboxColumns.SERVER_ID, MailboxColumns.SYNC_KEY }; } public static final long NO_MAILBOX = -1; // Sentinel values for the mSyncInterval field of both Mailbox records @Deprecated public static final int CHECK_INTERVAL_NEVER = -1; @Deprecated public static final int CHECK_INTERVAL_PUSH = -2; // The following two sentinel values are used by EAS // Ping indicates that the EAS mailbox is synced based on a "ping" from the server @Deprecated public static final int CHECK_INTERVAL_PING = -3; // Push-Hold indicates an EAS push or ping Mailbox shouldn't sync just yet @Deprecated public static final int CHECK_INTERVAL_PUSH_HOLD = -4; // Sentinel for PARENT_KEY. Use NO_MAILBOX for toplevel mailboxes (i.e. no parents). public static final long PARENT_KEY_UNINITIALIZED = 0L; private static final String WHERE_TYPE_AND_ACCOUNT_KEY = MailboxColumns.TYPE + "=? and " + MailboxColumns.ACCOUNT_KEY + "=?"; /** * Selection for mailboxes that should receive push for an account. A mailbox should receive * push if it has a valid, non-initial sync key and is opted in for sync. */ private static final String PUSH_MAILBOXES_FOR_ACCOUNT_SELECTION = MailboxColumns.SYNC_KEY + " is not null and " + MailboxColumns.SYNC_KEY + "!='' and " + MailboxColumns.SYNC_KEY + "!='0' and " + MailboxColumns.SYNC_INTERVAL + "=1 and " + MailboxColumns.ACCOUNT_KEY + "=?"; /** Selection for mailboxes that say they want to sync, plus outbox, for an account. */ private static final String OUTBOX_PLUS_SYNCING_AND_ACCOUNT_SELECTION = "(" + MailboxColumns.TYPE + "=" + Mailbox.TYPE_OUTBOX + " or " + MailboxColumns.SYNC_INTERVAL + "=1) and " + MailboxColumns.ACCOUNT_KEY + "=?"; /** Selection for mailboxes that are configured for sync of a certain type for an account. */ private static final String SYNCING_AND_TYPE_FOR_ACCOUNT_SELECTION = MailboxColumns.SYNC_INTERVAL + "=1 and " + MailboxColumns.TYPE + "=? and " + MailboxColumns.ACCOUNT_KEY + "=?"; /** Selection for mailboxes that are configured for sync for an account. */ private static final String SYNCING_MAILBOXES_FOR_ACCOUNT_SELECTION = MailboxColumns.SYNC_INTERVAL + "=1 and " + MailboxColumns.ACCOUNT_KEY + "=?"; // Types of mailboxes. The list is ordered to match a typical UI presentation, e.g. // placing the inbox at the top. // Arrays of "special_mailbox_display_names" and "special_mailbox_icons" are depends on // types Id of mailboxes. /** No type specified */ public static final int TYPE_NONE = -1; /** The "main" mailbox for the account, almost always referred to as "Inbox" */ public static final int TYPE_INBOX = 0; // Types of mailboxes /** Generic mailbox that holds mail */ public static final int TYPE_MAIL = 1; /** Parent-only mailbox; does not hold any mail */ public static final int TYPE_PARENT = 2; /** Drafts mailbox */ public static final int TYPE_DRAFTS = 3; /** Local mailbox associated with the account's outgoing mail */ public static final int TYPE_OUTBOX = 4; /** Sent mail; mail that was sent from the account */ public static final int TYPE_SENT = 5; /** Deleted mail */ public static final int TYPE_TRASH = 6; /** Junk mail */ public static final int TYPE_JUNK = 7; /** Search results */ public static final int TYPE_SEARCH = 8; /** Starred (virtual) */ public static final int TYPE_STARRED = 9; /** All unread mail (virtual) */ public static final int TYPE_UNREAD = 10; // Types after this are used for non-mail mailboxes (as in EAS) public static final int TYPE_NOT_EMAIL = 0x40; public static final int TYPE_CALENDAR = 0x41; public static final int TYPE_CONTACTS = 0x42; public static final int TYPE_TASKS = 0x43; @Deprecated public static final int TYPE_EAS_ACCOUNT_MAILBOX = 0x44; public static final int TYPE_UNKNOWN = 0x45; /** * Specifies which mailbox types may be synced from server, and what the default sync interval * value should be. * If a mailbox type is in this array, then it can be synced. * If the mailbox type is mapped to true in this array, then new mailboxes of that type should * be set to automatically sync (either with the periodic poll, or with push, as determined * by the account's sync settings). * See {@link #isSyncableType} and {@link #getDefaultSyncStateForType} for how to access this * data. */ private static final SparseBooleanArray SYNCABLE_TYPES; static { SYNCABLE_TYPES = new SparseBooleanArray(7); SYNCABLE_TYPES.put(TYPE_INBOX, true); SYNCABLE_TYPES.put(TYPE_MAIL, false); // TODO: b/11158759 // For now, drafts folders are not syncable. //SYNCABLE_TYPES.put(TYPE_DRAFTS, true); SYNCABLE_TYPES.put(TYPE_SENT, true); SYNCABLE_TYPES.put(TYPE_TRASH, false); SYNCABLE_TYPES.put(TYPE_CALENDAR, true); SYNCABLE_TYPES.put(TYPE_CONTACTS, true); } public static final int TYPE_NOT_SYNCABLE = 0x100; // A mailbox that holds Messages that are attachments public static final int TYPE_ATTACHMENT = 0x101; /** * For each of the following folder types, we expect there to be exactly one folder of that * type per account. * Each sync adapter must do the following: * 1) On initial sync: For each type that was not found from the server, create a local folder. * 2) On folder delete: If it's of a required type, convert it to local rather than delete. * 3) On folder add: If it's of a required type, convert the local folder to server. * 4) When adding a duplicate (either initial sync or folder add): Error. */ public static final int[] REQUIRED_FOLDER_TYPES = { TYPE_INBOX, TYPE_DRAFTS, TYPE_OUTBOX, TYPE_SENT, TYPE_TRASH }; // Default "touch" time for system mailboxes public static final int DRAFTS_DEFAULT_TOUCH_TIME = 2; public static final int SENT_DEFAULT_TOUCH_TIME = 1; // Bit field flags; each is defined below // Warning: Do not read these flags until POP/IMAP/EAS all populate them /** No flags set */ public static final int FLAG_NONE = 0; /** Has children in the mailbox hierarchy */ public static final int FLAG_HAS_CHILDREN = 1<<0; /** Children are visible in the UI */ public static final int FLAG_CHILDREN_VISIBLE = 1<<1; /** cannot receive "pushed" mail */ public static final int FLAG_CANT_PUSH = 1<<2; /** can hold emails (i.e. some parent mailboxes cannot themselves contain mail) */ public static final int FLAG_HOLDS_MAIL = 1<<3; /** can be used as a target for moving messages within the account */ public static final int FLAG_ACCEPTS_MOVED_MAIL = 1<<4; /** can be used as a target for appending messages */ public static final int FLAG_ACCEPTS_APPENDED_MAIL = 1<<5; /** has user settings (sync lookback, etc.) */ public static final int FLAG_SUPPORTS_SETTINGS = 1<<6; // Magic mailbox ID's // NOTE: This is a quick solution for merged mailboxes. I would rather implement this // with a more generic way of packaging and sharing queries between activities public static final long QUERY_ALL_INBOXES = -2; public static final long QUERY_ALL_UNREAD = -3; public static final long QUERY_ALL_FAVORITES = -4; public static final long QUERY_ALL_DRAFTS = -5; public static final long QUERY_ALL_OUTBOX = -6; public Mailbox() { mBaseUri = CONTENT_URI; } public static String getSystemMailboxName(Context context, int mailboxType) { final int resId; switch (mailboxType) { case Mailbox.TYPE_INBOX: resId = R.string.mailbox_name_server_inbox; break; case Mailbox.TYPE_OUTBOX: resId = R.string.mailbox_name_server_outbox; break; case Mailbox.TYPE_DRAFTS: resId = R.string.mailbox_name_server_drafts; break; case Mailbox.TYPE_TRASH: resId = R.string.mailbox_name_server_trash; break; case Mailbox.TYPE_SENT: resId = R.string.mailbox_name_server_sent; break; case Mailbox.TYPE_JUNK: resId = R.string.mailbox_name_server_junk; break; case Mailbox.TYPE_STARRED: resId = R.string.mailbox_name_server_starred; break; case Mailbox.TYPE_UNREAD: resId = R.string.mailbox_name_server_all_unread; break; default: throw new IllegalArgumentException("Illegal mailbox type"); } return context.getString(resId); } /** * Restore a Mailbox from the database, given its unique id * @param context Makes provider calls * @param id Row ID of mailbox to restore * @return the instantiated Mailbox */ public static Mailbox restoreMailboxWithId(Context context, long id) { return EmailContent.restoreContentWithId(context, Mailbox.class, Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION, id); } /** * Builds a new mailbox with "typical" settings for a system mailbox, such as a local "Drafts" * mailbox. This is useful for protocols like POP3 or IMAP who don't have certain local * system mailboxes synced with the server. * Note: the mailbox is not persisted - clients must call {@link #save} themselves. */ public static Mailbox newSystemMailbox(Context context, long accountId, int mailboxType) { // Sync interval and flags are different based on mailbox type. // TODO: Sync interval doesn't seem to be used anywhere, make it matter or get rid of it. final int syncInterval; final int flags; switch (mailboxType) { case TYPE_INBOX: flags = Mailbox.FLAG_HOLDS_MAIL | Mailbox.FLAG_ACCEPTS_MOVED_MAIL; syncInterval = 0; break; case TYPE_SENT: case TYPE_TRASH: flags = Mailbox.FLAG_HOLDS_MAIL; syncInterval = 0; break; case TYPE_DRAFTS: case TYPE_OUTBOX: flags = Mailbox.FLAG_HOLDS_MAIL; syncInterval = Account.CHECK_INTERVAL_NEVER; break; default: throw new IllegalArgumentException("Bad mailbox type for newSystemMailbox: " + mailboxType); } final Mailbox box = new Mailbox(); box.mAccountKey = accountId; box.mType = mailboxType; box.mSyncInterval = syncInterval; box.mFlagVisible = true; // TODO: Fix how display names work. box.mServerId = box.mDisplayName = getSystemMailboxName(context, mailboxType); box.mParentKey = Mailbox.NO_MAILBOX; box.mFlags = flags; return box; } /** * Returns a Mailbox from the database, given its pathname and account id. All mailbox * paths for a particular account must be unique. Paths are stored in the column * {@link MailboxColumns#SERVER_ID} for want of yet another column in the table. * @param context Makes provider calls * @param accountId the ID of the account * @param path the fully qualified, remote pathname */ public static Mailbox restoreMailboxForPath(Context context, long accountId, String path) { final Cursor c = context.getContentResolver().query( Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION, Mailbox.PATH_AND_ACCOUNT_SELECTION, new String[] { path, Long.toString(accountId) }, null); if (c == null) throw new ProviderUnavailableException(); try { Mailbox mailbox = null; if (c.moveToFirst()) { mailbox = getContent(context, c, Mailbox.class); if (c.moveToNext()) { LogUtils.w(Logging.LOG_TAG, "Multiple mailboxes named \"%s\"", path); } } else { LogUtils.i(Logging.LOG_TAG, "Could not find mailbox at \"%s\"", path); } return mailbox; } finally { c.close(); } } /** * Returns a {@link Mailbox} for the given path. If the path is not in the database, a new * mailbox will be created. */ public static Mailbox getMailboxForPath(Context context, long accountId, String path) { Mailbox mailbox = restoreMailboxForPath(context, accountId, path); if (mailbox == null) { mailbox = new Mailbox(); } return mailbox; } /** * Check if a mailbox type can be synced with the server. * @param mailboxType The type to check. * @return Whether this type is syncable. */ public static boolean isSyncableType(final int mailboxType) { return SYNCABLE_TYPES.indexOfKey(mailboxType) >= 0; } /** * Check if a mailbox type should sync with the server by default. * @param mailboxType The type to check. * @return Whether this type should default to syncing. */ public static boolean getDefaultSyncStateForType(final int mailboxType) { return SYNCABLE_TYPES.get(mailboxType); } /** * Check whether this mailbox is syncable. It has to be both a server synced mailbox, and * of a syncable able. * @return Whether this mailbox is syncable. */ public boolean isSyncable() { return (mTotalCount >= 0) && isSyncableType(mType); } @Override public void restore(Cursor cursor) { mBaseUri = CONTENT_URI; mId = cursor.getLong(CONTENT_ID_COLUMN); mDisplayName = cursor.getString(CONTENT_DISPLAY_NAME_COLUMN); mServerId = cursor.getString(CONTENT_SERVER_ID_COLUMN); mParentServerId = cursor.getString(CONTENT_PARENT_SERVER_ID_COLUMN); mParentKey = cursor.getLong(CONTENT_PARENT_KEY_COLUMN); mAccountKey = cursor.getLong(CONTENT_ACCOUNT_KEY_COLUMN); mType = cursor.getInt(CONTENT_TYPE_COLUMN); mDelimiter = cursor.getInt(CONTENT_DELIMITER_COLUMN); mSyncKey = cursor.getString(CONTENT_SYNC_KEY_COLUMN); mSyncLookback = cursor.getInt(CONTENT_SYNC_LOOKBACK_COLUMN); mSyncInterval = cursor.getInt(CONTENT_SYNC_INTERVAL_COLUMN); mSyncTime = cursor.getLong(CONTENT_SYNC_TIME_COLUMN); mFlagVisible = cursor.getInt(CONTENT_FLAG_VISIBLE_COLUMN) == 1; mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN); mSyncStatus = cursor.getString(CONTENT_SYNC_STATUS_COLUMN); mLastTouchedTime = cursor.getLong(CONTENT_LAST_TOUCHED_TIME_COLUMN); mUiSyncStatus = cursor.getInt(CONTENT_UI_SYNC_STATUS_COLUMN); mUiLastSyncResult = cursor.getInt(CONTENT_UI_LAST_SYNC_RESULT_COLUMN); mTotalCount = cursor.getInt(CONTENT_TOTAL_COUNT_COLUMN); mHierarchicalName = cursor.getString(CONTENT_HIERARCHICAL_NAME_COLUMN); mLastFullSyncTime = cursor.getInt(CONTENT_LAST_FULL_SYNC_COLUMN); } @Override public ContentValues toContentValues() { final ContentValues values = new ContentValues(20); values.put(MailboxColumns.DISPLAY_NAME, mDisplayName); values.put(MailboxColumns.SERVER_ID, mServerId); values.put(MailboxColumns.PARENT_SERVER_ID, mParentServerId); values.put(MailboxColumns.PARENT_KEY, mParentKey); values.put(MailboxColumns.ACCOUNT_KEY, mAccountKey); values.put(MailboxColumns.TYPE, mType); values.put(MailboxColumns.DELIMITER, mDelimiter); values.put(MailboxColumns.SYNC_KEY, mSyncKey); values.put(MailboxColumns.SYNC_LOOKBACK, mSyncLookback); values.put(MailboxColumns.SYNC_INTERVAL, mSyncInterval); values.put(MailboxColumns.SYNC_TIME, mSyncTime); values.put(MailboxColumns.FLAG_VISIBLE, mFlagVisible); values.put(MailboxColumns.FLAGS, mFlags); values.put(MailboxColumns.SYNC_STATUS, mSyncStatus); values.put(MailboxColumns.LAST_TOUCHED_TIME, mLastTouchedTime); values.put(MailboxColumns.UI_SYNC_STATUS, mUiSyncStatus); values.put(MailboxColumns.UI_LAST_SYNC_RESULT, mUiLastSyncResult); values.put(MailboxColumns.TOTAL_COUNT, mTotalCount); values.put(MailboxColumns.HIERARCHICAL_NAME, mHierarchicalName); values.put(MailboxColumns.LAST_FULL_SYNC_TIME, mLastFullSyncTime); return values; } /** * Store the updated message count in the database. * @param c Makes provider calls * @param count New count */ public void updateMessageCount(final Context c, final int count) { if (count != mTotalCount) { final ContentValues values = new ContentValues(1); values.put(MailboxColumns.TOTAL_COUNT, count); update(c, values); mTotalCount = count; } } /** * Store the last full sync time in the database. * @param c Makes provider calls * @param syncTime New syncTime */ public void updateLastFullSyncTime(final Context c, final long syncTime) { if (syncTime != mLastFullSyncTime) { final ContentValues values = new ContentValues(1); values.put(MailboxColumns.LAST_FULL_SYNC_TIME, syncTime); update(c, values); mLastFullSyncTime = syncTime; } } /** * Convenience method to return the id of a given type of Mailbox for a given Account; the * common Mailbox types (Inbox, Outbox, Sent, Drafts, Trash, and Search) are all cached by * EmailProvider; therefore, we warn if the mailbox is not found in the cache * * @param context the caller's context, used to get a ContentResolver * @param accountId the id of the account to be queried * @param type the mailbox type, as defined above * @return the id of the mailbox, or -1 if not found */ public static long findMailboxOfType(Context context, long accountId, int type) { final String[] bindArguments = new String[] {Long.toString(type), Long.toString(accountId)}; return Utility.getFirstRowLong(context, Mailbox.CONTENT_URI, ID_PROJECTION, WHERE_TYPE_AND_ACCOUNT_KEY, bindArguments, null, ID_PROJECTION_COLUMN, NO_MAILBOX); } /** * Convenience method that returns the mailbox found using the method above */ public static Mailbox restoreMailboxOfType(Context context, long accountId, int type) { final long mailboxId = findMailboxOfType(context, accountId, type); if (mailboxId != Mailbox.NO_MAILBOX) { return Mailbox.restoreMailboxWithId(context, mailboxId); } return null; } /** * Return the mailbox for a message with a given id * @param context the caller's context * @param messageId the id of the message * @return the mailbox, or null if the mailbox doesn't exist */ public static Mailbox getMailboxForMessageId(Context context, long messageId) { final long mailboxId = Message.getKeyColumnLong(context, messageId, MessageColumns.MAILBOX_KEY); if (mailboxId != -1) { return Mailbox.restoreMailboxWithId(context, mailboxId); } return null; } /** * @return mailbox type, or -1 if mailbox not found. */ public static int getMailboxType(Context context, long mailboxId) { final Uri url = ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailboxId); return Utility.getFirstRowInt(context, url, MAILBOX_TYPE_PROJECTION, null, null, null, MAILBOX_TYPE_TYPE_COLUMN, -1); } /** * @return mailbox display name, or null if mailbox not found. */ public static String getDisplayName(Context context, long mailboxId) { final Uri url = ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailboxId); return Utility.getFirstRowString(context, url, MAILBOX_DISPLAY_NAME_PROJECTION, null, null, null, MAILBOX_DISPLAY_NAME_COLUMN); } /** * @param mailboxId ID of a mailbox. This method accepts magic mailbox IDs, such as * {@link #QUERY_ALL_INBOXES}. (They're all non-refreshable.) * @return true if a mailbox is refreshable. */ public static boolean isRefreshable(Context context, long mailboxId) { if (mailboxId < 0) { return false; // magic mailboxes } switch (getMailboxType(context, mailboxId)) { case -1: // not found case TYPE_DRAFTS: case TYPE_OUTBOX: return false; } return true; } /** * @return whether or not this mailbox supports moving messages out of it */ public boolean canHaveMessagesMoved() { switch (mType) { case TYPE_INBOX: case TYPE_MAIL: case TYPE_TRASH: case TYPE_JUNK: return true; } return false; // TYPE_DRAFTS, TYPE_OUTBOX, TYPE_SENT, etc } /** * Returns a set of hashes that can identify this mailbox. These can be used to * determine if any of the fields have been modified. */ public Object[] getHashes() { final Object[] hash = new Object[CONTENT_PROJECTION.length]; hash[CONTENT_ID_COLUMN] = mId; hash[CONTENT_DISPLAY_NAME_COLUMN] = mDisplayName; hash[CONTENT_SERVER_ID_COLUMN] = mServerId; hash[CONTENT_PARENT_SERVER_ID_COLUMN] = mParentServerId; hash[CONTENT_ACCOUNT_KEY_COLUMN] = mAccountKey; hash[CONTENT_TYPE_COLUMN] = mType; hash[CONTENT_DELIMITER_COLUMN] = mDelimiter; hash[CONTENT_SYNC_KEY_COLUMN] = mSyncKey; hash[CONTENT_SYNC_LOOKBACK_COLUMN] = mSyncLookback; hash[CONTENT_SYNC_INTERVAL_COLUMN] = mSyncInterval; hash[CONTENT_SYNC_TIME_COLUMN] = mSyncTime; hash[CONTENT_FLAG_VISIBLE_COLUMN] = mFlagVisible; hash[CONTENT_FLAGS_COLUMN] = mFlags; hash[CONTENT_SYNC_STATUS_COLUMN] = mSyncStatus; hash[CONTENT_PARENT_KEY_COLUMN] = mParentKey; hash[CONTENT_LAST_TOUCHED_TIME_COLUMN] = mLastTouchedTime; hash[CONTENT_UI_SYNC_STATUS_COLUMN] = mUiSyncStatus; hash[CONTENT_UI_LAST_SYNC_RESULT_COLUMN] = mUiLastSyncResult; hash[CONTENT_TOTAL_COUNT_COLUMN] = mTotalCount; hash[CONTENT_HIERARCHICAL_NAME_COLUMN] = mHierarchicalName; return hash; } // Parcelable @Override public int describeContents() { return 0; } // Parcelable @Override public void writeToParcel(Parcel dest, int flags) { dest.writeParcelable(mBaseUri, flags); dest.writeLong(mId); dest.writeString(mDisplayName); dest.writeString(mServerId); dest.writeString(mParentServerId); dest.writeLong(mParentKey); dest.writeLong(mAccountKey); dest.writeInt(mType); dest.writeInt(mDelimiter); dest.writeString(mSyncKey); dest.writeInt(mSyncLookback); dest.writeInt(mSyncInterval); dest.writeLong(mSyncTime); dest.writeInt(mFlagVisible ? 1 : 0); dest.writeInt(mFlags); dest.writeString(mSyncStatus); dest.writeLong(mLastTouchedTime); dest.writeInt(mUiSyncStatus); dest.writeInt(mUiLastSyncResult); dest.writeInt(mTotalCount); dest.writeString(mHierarchicalName); dest.writeLong(mLastFullSyncTime); } public Mailbox(Parcel in) { mBaseUri = in.readParcelable(null); mId = in.readLong(); mDisplayName = in.readString(); mServerId = in.readString(); mParentServerId = in.readString(); mParentKey = in.readLong(); mAccountKey = in.readLong(); mType = in.readInt(); mDelimiter = in.readInt(); mSyncKey = in.readString(); mSyncLookback = in.readInt(); mSyncInterval = in.readInt(); mSyncTime = in.readLong(); mFlagVisible = in.readInt() == 1; mFlags = in.readInt(); mSyncStatus = in.readString(); mLastTouchedTime = in.readLong(); mUiSyncStatus = in.readInt(); mUiLastSyncResult = in.readInt(); mTotalCount = in.readInt(); mHierarchicalName = in.readString(); mLastFullSyncTime = in.readLong(); } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public Mailbox createFromParcel(Parcel source) { return new Mailbox(source); } @Override public Mailbox[] newArray(int size) { return new Mailbox[size]; } }; @Override public String toString() { return "[Mailbox " + mId + ": " + mDisplayName + "]"; } /** * Get the mailboxes that should receive push updates for an account. * @param cr The {@link ContentResolver}. * @param accountId The id for the account that is pushing. * @return A cursor (suitable for use with {@link #restore}) with all mailboxes we should sync. */ public static Cursor getMailboxesForPush(final ContentResolver cr, final long accountId) { return cr.query(Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION, PUSH_MAILBOXES_FOR_ACCOUNT_SELECTION, new String[] { Long.toString(accountId) }, null); } /** * Get the mailbox ids for an account that should sync when we do a full account sync. * @param cr The {@link ContentResolver}. * @param accountId The id for the account that is pushing. * @return A cursor (with one column, containing ids) with all mailbox ids we should sync. */ public static Cursor getMailboxIdsForSync(final ContentResolver cr, final long accountId) { // We're sorting by mailbox type. The reason is that the inbox is type 0, other types // (e.g. Calendar and Contacts) are all higher numbers. Upon initial sync, we'd like to // sync the inbox first to improve perceived performance. return cr.query(Mailbox.CONTENT_URI, Mailbox.ID_PROJECTION, OUTBOX_PLUS_SYNCING_AND_ACCOUNT_SELECTION, new String[] { Long.toString(accountId) }, MailboxColumns.TYPE + " ASC"); } /** * Get the mailbox ids for an account that are configured for sync and have a specific type. * @param accountId The id for the account that is syncing. * @param mailboxType The type of the mailbox we're interested in. * @return A cursor (with one column, containing ids) with all mailbox ids that match. */ public static Cursor getMailboxIdsForSyncByType(final ContentResolver cr, final long accountId, final int mailboxType) { return cr.query(Mailbox.CONTENT_URI, Mailbox.ID_PROJECTION, SYNCING_AND_TYPE_FOR_ACCOUNT_SELECTION, new String[] { Integer.toString(mailboxType), Long.toString(accountId) }, null); } /** * Get the mailbox ids for an account that are configured for sync by the user. * @param cr The {@link ContentResolver}. * @param accountId The id for the account that is syncing. * @return A cursor (with one column, containing ids) with all mailbox ids that match. */ public static Cursor getLoopBackMailboxIdsForSync(final ContentResolver cr, final long accountId) { return cr.query(Mailbox.CONTENT_URI, Mailbox.ID_PROJECTION, SYNCING_MAILBOXES_FOR_ACCOUNT_SELECTION, new String[] {Long.toString(accountId) }, null); } /** * Get the account id for a mailbox. * @param context The {@link Context}. * @param mailboxId The id of the mailbox we're interested in, as a {@link String}. * @return The account id for the mailbox, or {@link Account#NO_ACCOUNT} if the mailbox doesn't * exist. */ public static long getAccountIdForMailbox(final Context context, final String mailboxId) { return Utility.getFirstRowLong(context, Mailbox.CONTENT_URI.buildUpon().appendEncodedPath(mailboxId).build(), ACCOUNT_KEY_PROJECTION, null, null, null, ACCOUNT_KEY_PROJECTION_ACCOUNT_KEY_COLUMN, Account.NO_ACCOUNT); } /** * Gets the correct authority for a mailbox. * @param mailboxType The type of the mailbox we're interested in. * @return The authority for the mailbox we're interested in. */ public static String getAuthority(final int mailboxType) { switch (mailboxType) { case Mailbox.TYPE_CALENDAR: return CalendarContract.AUTHORITY; case Mailbox.TYPE_CONTACTS: return ContactsContract.AUTHORITY; default: return EmailContent.AUTHORITY; } } public static void resyncMailbox(final ContentResolver cr, final android.accounts.Account account, final long mailboxId) { final Cursor cursor = cr.query(Mailbox.CONTENT_URI, new String[]{ MailboxColumns.TYPE, MailboxColumns.SERVER_ID, }, MailboxColumns._ID + "=?", new String[] {String.valueOf(mailboxId)}, null); if (cursor == null || cursor.getCount() == 0) { LogUtils.w(Logging.LOG_TAG, "Mailbox %d not found", mailboxId); return; } try { cursor.moveToFirst(); final int type = cursor.getInt(0); if (type >= TYPE_NOT_EMAIL) { throw new IllegalArgumentException( String.format("Mailbox %d is not an Email mailbox", mailboxId)); } final String serverId = cursor.getString(1); if (TextUtils.isEmpty(serverId)) { throw new IllegalArgumentException( String.format("Mailbox %d has no server id", mailboxId)); } final ArrayList ops = new ArrayList(); ops.add(ContentProviderOperation.newDelete(Message.CONTENT_URI) .withSelection(Message.MAILBOX_SELECTION, new String[]{String.valueOf(mailboxId)}) .build()); ops.add(ContentProviderOperation.newUpdate( ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailboxId)) .withValue(MailboxColumns.SYNC_KEY, "0").build()); cr.applyBatch(AUTHORITY, ops); final Bundle extras = createSyncBundle(mailboxId); extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true); ContentResolver.requestSync(account, AUTHORITY, extras); LogUtils.i(Logging.LOG_TAG, "requestSync resyncMailbox %s, %s", account.toString(), extras.toString()); } catch (RemoteException e) { LogUtils.w(Logging.LOG_TAG, e, "Failed to wipe mailbox %d", mailboxId); } catch (OperationApplicationException e) { LogUtils.w(Logging.LOG_TAG, e, "Failed to wipe mailbox %d", mailboxId); } finally { cursor.close(); } } }