From 6e418aa41a17136be0dddb816d843428a0a1e722 Mon Sep 17 00:00:00 2001 From: Marc Blank Date: Sat, 18 Jun 2011 18:03:11 -0700 Subject: [PATCH] Improve EmailContent caching... * Guarantee that up to 16 Account (with HostAuths), and Policy rows are always cached. Also, 6 commonly used Mailboxes per Account (Inbox, Outbox, Drafts, Sent, Trash, and Search) * Precache these rows when EmailProvider starts up * Ensure that newly added, precachable rows are cached when created * Clean up some inefficient/wrong caching code * Fix a commonly called method in NotificationManager in which we load a single Mailbox row using selection vs withAppendedId * Confirm that we don't read from the database in typical use and heavy message loading * Add a special URI for finding mailbox by type (using the cache) * Add special-case code for EmailContent.count(Account.CONTENT_URI) which is used in a number of places (including on the UI thread) and whose value is easily determined * Add a special URI to get the default account id * Confirm that all unit tests work The goal here is to be able to load all Account, HostAuth, Policy, and Mailbox objects (by id) without worrying about disk access. There will still be a single disk read for uncommon Mailbox reads, but this should be considered acceptable. Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210 TODO: Unit tests --- .../android/emailcommon/provider/Account.java | 21 +- .../android/emailcommon/provider/Mailbox.java | 18 +- .../android/email/NotificationController.java | 39 +- .../android/email/provider/ContentCache.java | 22 +- .../android/email/provider/EmailProvider.java | 375 +++++++++++++++--- .../email/ControllerProviderOpsTests.java | 2 +- tests/src/com/android/email/DBTestHelper.java | 2 +- .../android/email/SecurityPolicyTests.java | 2 +- .../email/activity/MailboxFinderTest.java | 10 +- .../email/activity/MessageComposeTests.java | 24 +- .../activity/RecentMailboxManagerTest.java | 8 +- .../android/email/provider/PolicyTests.java | 2 +- .../android/email/provider/ProviderTests.java | 144 ++++++- .../emailcommon/provider/MailboxTests.java | 2 +- .../provider/QuickResponseTests.java | 16 +- 15 files changed, 526 insertions(+), 161 deletions(-) diff --git a/emailcommon/src/com/android/emailcommon/provider/Account.java b/emailcommon/src/com/android/emailcommon/provider/Account.java index 8f0d093f4..7accab1b7 100644 --- a/emailcommon/src/com/android/emailcommon/provider/Account.java +++ b/emailcommon/src/com/android/emailcommon/provider/Account.java @@ -30,6 +30,8 @@ public final class Account extends EmailContent implements AccountColumns, Parce Uri.parse(EmailContent.CONTENT_URI + "/resetNewMessageCount"); public static final Uri NOTIFIER_URI = Uri.parse(EmailContent.CONTENT_NOTIFIER_URI + "/account"); + public static final Uri DEFAULT_ACCOUNT_ID_URI = + Uri.parse(EmailContent.CONTENT_URI + "/account/default"); // Define all pseudo account IDs here to avoid conflict with one another. /** @@ -545,18 +547,21 @@ public final class Account extends EmailContent implements AccountColumns, Parce /** * Return the id of the default account. If one hasn't been explicitly specified, return - * the first one in the database. For any account saved in the DB, this must be used - * to check for the default account - the mIsDefault field is set lazily and may be - * incorrect. + * the first one in the database (the logic is provided within EmailProvider) * @param context the caller's context * @return the id of the default account, or -1 if there are no accounts */ static public long getDefaultAccountId(Context context) { - long id = getDefaultAccountWhere(context, AccountColumns.IS_DEFAULT + "=1"); - if (id == -1) { - id = getDefaultAccountWhere(context, null); + Cursor c = context.getContentResolver().query( + Account.DEFAULT_ACCOUNT_ID_URI, Account.ID_PROJECTION, null, null, null); + try { + if (c != null && c.moveToFirst()) { + return c.getLong(Account.ID_PROJECTION_COLUMN); + } + } finally { + c.close(); } - return id; + return -1; } /** @@ -755,7 +760,7 @@ public final class Account extends EmailContent implements AccountColumns, Parce // Now do the Account ContentValues cv = null; - if (recvIndex >= 0 || sendIndex >= 0) { + if (recvIndex >= 0 || sendIndex >= 0 || policyIndex >= 0) { cv = new ContentValues(); if (recvIndex >= 0) { cv.put(Account.HOST_AUTH_KEY_RECV, recvIndex); diff --git a/emailcommon/src/com/android/emailcommon/provider/Mailbox.java b/emailcommon/src/com/android/emailcommon/provider/Mailbox.java index 589a3a0d5..a3d8daae9 100644 --- a/emailcommon/src/com/android/emailcommon/provider/Mailbox.java +++ b/emailcommon/src/com/android/emailcommon/provider/Mailbox.java @@ -37,6 +37,8 @@ public class Mailbox extends EmailContent implements SyncColumns, MailboxColumns public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/mailbox"); public static final Uri ADD_TO_FIELD_URI = Uri.parse(EmailContent.CONTENT_URI + "/mailboxIdAddToField"); + public static final Uri FROM_ACCOUNT_AND_TYPE_URI = + Uri.parse(EmailContent.CONTENT_URI + "/mailboxIdFromAccountAndType"); public String mDisplayName; public String mServerId; @@ -299,13 +301,27 @@ public class Mailbox extends EmailContent implements SyncColumns, MailboxColumns } /** - * Convenience method to return the id of a given type of Mailbox for a given Account + * 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) { + // First use special URI + Uri uri = FROM_ACCOUNT_AND_TYPE_URI.buildUpon().appendPath(Long.toString(accountId)) + .appendPath(Integer.toString(type)).build(); + Cursor c = context.getContentResolver().query(uri, ID_PROJECTION, null, null, null); + c.moveToFirst(); + Long mailboxId = c.getLong(ID_PROJECTION_COLUMN); + if (mailboxId != null && mailboxId.intValue() != 0) { + return mailboxId; + } else { + Log.w(Logging.LOG_TAG, "========== Mailbox of type " + type + " not found in cache??"); + } 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, diff --git a/src/com/android/email/NotificationController.java b/src/com/android/email/NotificationController.java index b947e6f17..8fc8b80d3 100644 --- a/src/com/android/email/NotificationController.java +++ b/src/com/android/email/NotificationController.java @@ -16,28 +16,12 @@ package com.android.email; -import com.android.email.activity.ContactStatusLoader; -import com.android.email.activity.Welcome; -import com.android.email.activity.setup.AccountSecurity; -import com.android.email.activity.setup.AccountSettings; -import com.android.emailcommon.Logging; -import com.android.emailcommon.mail.Address; -import com.android.emailcommon.provider.Account; -import com.android.emailcommon.provider.EmailContent; -import com.android.emailcommon.provider.EmailContent.Attachment; -import com.android.emailcommon.provider.EmailContent.MailboxColumns; -import com.android.emailcommon.provider.EmailContent.Message; -import com.android.emailcommon.provider.EmailContent.MessageColumns; -import com.android.emailcommon.provider.Mailbox; -import com.android.emailcommon.utility.Utility; - -import com.google.common.annotations.VisibleForTesting; - import android.app.Notification; import android.app.Notification.Builder; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.ContentResolver; +import android.content.ContentUris; import android.content.Context; import android.content.Intent; import android.database.ContentObserver; @@ -54,6 +38,22 @@ import android.text.TextUtils; import android.text.style.TextAppearanceSpan; import android.util.Log; +import com.android.email.activity.ContactStatusLoader; +import com.android.email.activity.Welcome; +import com.android.email.activity.setup.AccountSecurity; +import com.android.email.activity.setup.AccountSettings; +import com.android.emailcommon.Logging; +import com.android.emailcommon.mail.Address; +import com.android.emailcommon.provider.Account; +import com.android.emailcommon.provider.EmailContent; +import com.android.emailcommon.provider.EmailContent.Attachment; +import com.android.emailcommon.provider.EmailContent.MailboxColumns; +import com.android.emailcommon.provider.EmailContent.Message; +import com.android.emailcommon.provider.EmailContent.MessageColumns; +import com.android.emailcommon.provider.Mailbox; +import com.android.emailcommon.utility.Utility; +import com.google.common.annotations.VisibleForTesting; + import java.util.HashMap; import java.util.HashSet; @@ -608,10 +608,9 @@ public class NotificationController { ContentResolver resolver = mContext.getContentResolver(); long lastSeenMessageId = Utility.getFirstRowLong( - mContext, Mailbox.CONTENT_URI, + mContext, ContentUris.withAppendedId(Mailbox.CONTENT_URI, mMailboxId), new String[] { MailboxColumns.LAST_SEEN_MESSAGE_KEY }, - EmailContent.ID_SELECTION, - new String[] { Long.toString(mMailboxId) }, null, 0, 0L); + null, null, null, 0, 0L); Cursor c = resolver.query( Message.CONTENT_URI, EmailContent.ID_PROJECTION, MESSAGE_SELECTION, diff --git a/src/com/android/email/provider/ContentCache.java b/src/com/android/email/provider/ContentCache.java index ab3f37f28..492fc8e98 100644 --- a/src/com/android/email/provider/ContentCache.java +++ b/src/com/android/email/provider/ContentCache.java @@ -16,8 +16,6 @@ package com.android.email.provider; -import com.android.email.Email; - import android.content.ContentValues; import android.database.Cursor; import android.database.CursorWrapper; @@ -25,6 +23,10 @@ import android.database.MatrixCursor; import android.net.Uri; import android.util.Log; import android.util.LruCache; + +import com.android.email.Email; +import com.google.common.annotations.VisibleForTesting; + import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -89,7 +91,7 @@ public final class ContentCache { private static final ArrayList sContentCaches = new ArrayList(); // A set of all unclosed, cached cursors; this will typically be a very small set, as cursors // tend to be closed quickly after use. The value, for each cursor, is its reference count - /*package*/ static CounterMap sActiveCursors; + /*package*/ static final CounterMap sActiveCursors = new CounterMap(24); // A set of locked content id's private final CounterMap mLockMap = new CounterMap(4); @@ -404,7 +406,6 @@ public final class ContentCache { mLogTag = "ContentCache-" + name; sContentCaches.add(this); mTokenList = new TokenList(mName); - sActiveCursors = new CounterMap(maxSize); mStats = new Statistics(this); } @@ -436,10 +437,14 @@ public final class ContentCache { return mLruCache.size(); } - private Cursor get(String id) { + @VisibleForTesting + Cursor get(String id) { return mLruCache.get(id); } + protected Map getSnapshot() { + return mLruCache.snapshot(); + } /** * Try to cache a cursor for the given id and projection; returns a valid cursor, either a * cached cursor (if caching was successful) or the original cursor @@ -456,7 +461,6 @@ public final class ContentCache { c.moveToPosition(0); return putCursorImpl(c, id, projection, token); } - public synchronized Cursor putCursorImpl(Cursor c, String id, String[] projection, CacheToken token) { try { @@ -467,7 +471,7 @@ public final class ContentCache { mStats.mStaleCount++; return c; } - if (c != null && projection == mBaseProjection && !sLockCache) { + if (c != null && Arrays.equals(projection, mBaseProjection) && !sLockCache) { if (Email.DEBUG && DEBUG_CACHE) { Log.d(mLogTag, "============ Caching cursor for: " + id); } @@ -723,7 +727,7 @@ public final class ContentCache { } // For use with unit tests - public static void invalidateAllCachesForTest() { + public static void invalidateAllCaches() { for (ContentCache cache: sContentCaches) { cache.invalidate(); } @@ -733,7 +737,7 @@ public final class ContentCache { public static void setLockCacheForTest(boolean lock) { sLockCache = lock; if (sLockCache) { - invalidateAllCachesForTest(); + invalidateAllCaches(); } } diff --git a/src/com/android/email/provider/EmailProvider.java b/src/com/android/email/provider/EmailProvider.java index 3b2969074..6f6aac830 100644 --- a/src/com/android/email/provider/EmailProvider.java +++ b/src/com/android/email/provider/EmailProvider.java @@ -16,34 +16,6 @@ package com.android.email.provider; -import com.android.email.Email; -import com.android.email.Preferences; -import com.android.email.provider.ContentCache.CacheToken; -import com.android.email.service.AttachmentDownloadService; -import com.android.emailcommon.AccountManagerTypes; -import com.android.emailcommon.CalendarProviderStub; -import com.android.emailcommon.Logging; -import com.android.emailcommon.provider.Account; -import com.android.emailcommon.provider.EmailContent; -import com.android.emailcommon.provider.EmailContent.AccountColumns; -import com.android.emailcommon.provider.EmailContent.Attachment; -import com.android.emailcommon.provider.EmailContent.AttachmentColumns; -import com.android.emailcommon.provider.EmailContent.Body; -import com.android.emailcommon.provider.EmailContent.BodyColumns; -import com.android.emailcommon.provider.QuickResponse; -import com.android.emailcommon.provider.EmailContent.QuickResponseColumns; -import com.android.emailcommon.provider.EmailContent.HostAuthColumns; -import com.android.emailcommon.provider.EmailContent.MailboxColumns; -import com.android.emailcommon.provider.EmailContent.Message; -import com.android.emailcommon.provider.EmailContent.MessageColumns; -import com.android.emailcommon.provider.EmailContent.PolicyColumns; -import com.android.emailcommon.provider.EmailContent.SyncColumns; -import com.android.emailcommon.provider.HostAuth; -import com.android.emailcommon.provider.Mailbox; -import com.android.emailcommon.provider.Policy; -import com.android.emailcommon.service.LegacyPolicySet; -import com.google.common.annotations.VisibleForTesting; - import android.accounts.AccountManager; import android.content.ContentProvider; import android.content.ContentProviderOperation; @@ -66,8 +38,40 @@ import android.provider.ContactsContract; import android.text.TextUtils; import android.util.Log; +import com.android.email.Email; +import com.android.email.Preferences; +import com.android.email.provider.ContentCache.CacheToken; +import com.android.email.service.AttachmentDownloadService; +import com.android.emailcommon.AccountManagerTypes; +import com.android.emailcommon.CalendarProviderStub; +import com.android.emailcommon.Logging; +import com.android.emailcommon.provider.Account; +import com.android.emailcommon.provider.EmailContent; +import com.android.emailcommon.provider.EmailContent.AccountColumns; +import com.android.emailcommon.provider.EmailContent.Attachment; +import com.android.emailcommon.provider.EmailContent.AttachmentColumns; +import com.android.emailcommon.provider.EmailContent.Body; +import com.android.emailcommon.provider.EmailContent.BodyColumns; +import com.android.emailcommon.provider.EmailContent.HostAuthColumns; +import com.android.emailcommon.provider.EmailContent.MailboxColumns; +import com.android.emailcommon.provider.EmailContent.Message; +import com.android.emailcommon.provider.EmailContent.MessageColumns; +import com.android.emailcommon.provider.EmailContent.PolicyColumns; +import com.android.emailcommon.provider.EmailContent.QuickResponseColumns; +import com.android.emailcommon.provider.EmailContent.SyncColumns; +import com.android.emailcommon.provider.HostAuth; +import com.android.emailcommon.provider.Mailbox; +import com.android.emailcommon.provider.Policy; +import com.android.emailcommon.provider.QuickResponse; +import com.android.emailcommon.service.LegacyPolicySet; +import com.google.common.annotations.VisibleForTesting; + import java.io.File; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; public class EmailProvider extends ContentProvider { @@ -106,17 +110,24 @@ public class EmailProvider extends ContentProvider { private static final String WHERE_ID = EmailContent.RECORD_ID + "=?"; + // This is not a hard limit on accounts, per se, but beyond this, we can't guarantee that all + // critical mailboxes, host auth's, accounts, and policies are cached + private static final int MAX_CACHED_ACCOUNTS = 16; + // Inbox, Drafts, Sent, Outbox, Trash, and Search (these boxes are cached when possible) + private static final int NUM_ALWAYS_CACHED_MAILBOXES = 6; + // We'll cache the following four tables; sizes are best estimates of effective values - private static final ContentCache sCacheAccount = - new ContentCache("Account", Account.CONTENT_PROJECTION, 4); - private static final ContentCache sCacheHostAuth = - new ContentCache("HostAuth", HostAuth.CONTENT_PROJECTION, 8); - /*package*/ static final ContentCache sCacheMailbox = - new ContentCache("Mailbox", Mailbox.CONTENT_PROJECTION, 8); - private static final ContentCache sCacheMessage = + private final ContentCache mCacheAccount = + new ContentCache("Account", Account.CONTENT_PROJECTION, MAX_CACHED_ACCOUNTS); + private final ContentCache mCacheHostAuth = + new ContentCache("HostAuth", HostAuth.CONTENT_PROJECTION, MAX_CACHED_ACCOUNTS * 2); + /*package*/ final ContentCache mCacheMailbox = + new ContentCache("Mailbox", Mailbox.CONTENT_PROJECTION, + MAX_CACHED_ACCOUNTS * (NUM_ALWAYS_CACHED_MAILBOXES + 2)); + private final ContentCache mCacheMessage = new ContentCache("Message", Message.CONTENT_PROJECTION, 8); - private static final ContentCache sCachePolicy = - new ContentCache("Policy", Policy.CONTENT_PROJECTION, 4); + private final ContentCache mCachePolicy = + new ContentCache("Policy", Policy.CONTENT_PROJECTION, MAX_CACHED_ACCOUNTS); // Any changes to the database format *must* include update-in-place code. // Original version: 3 @@ -162,10 +173,12 @@ public class EmailProvider extends ContentProvider { private static final int ACCOUNT_ID_ADD_TO_FIELD = ACCOUNT_BASE + 2; private static final int ACCOUNT_RESET_NEW_COUNT = ACCOUNT_BASE + 3; private static final int ACCOUNT_RESET_NEW_COUNT_ID = ACCOUNT_BASE + 4; + private static final int ACCOUNT_DEFAULT_ID = ACCOUNT_BASE + 5; private static final int MAILBOX_BASE = 0x1000; private static final int MAILBOX = MAILBOX_BASE; private static final int MAILBOX_ID = MAILBOX_BASE + 1; + private static final int MAILBOX_ID_FROM_ACCOUNT_AND_TYPE = MAILBOX_BASE + 2; private static final int MAILBOX_ID_ADD_TO_FIELD = MAILBOX_BASE + 2; private static final int MESSAGE_BASE = 0x2000; @@ -214,32 +227,50 @@ public class EmailProvider extends ContentProvider { private static final String[] TABLE_NAMES = { Account.TABLE_NAME, Mailbox.TABLE_NAME, - EmailContent.Message.TABLE_NAME, - EmailContent.Attachment.TABLE_NAME, + Message.TABLE_NAME, + Attachment.TABLE_NAME, HostAuth.TABLE_NAME, - EmailContent.Message.UPDATED_TABLE_NAME, - EmailContent.Message.DELETED_TABLE_NAME, + Message.UPDATED_TABLE_NAME, + Message.DELETED_TABLE_NAME, Policy.TABLE_NAME, QuickResponse.TABLE_NAME, - EmailContent.Body.TABLE_NAME + Body.TABLE_NAME }; // CONTENT_CACHES MUST remain in the order of the BASE constants above - private static final ContentCache[] CONTENT_CACHES = { - sCacheAccount, - sCacheMailbox, - sCacheMessage, + private final ContentCache[] mContentCaches = { + mCacheAccount, + mCacheMailbox, + mCacheMessage, null, // Attachment - sCacheHostAuth, + mCacheHostAuth, null, // Updated message null, // Deleted message - sCachePolicy, + mCachePolicy, + null, // Quick response + null // Body + }; + + // CACHE_PROJECTIONS MUST remain in the order of the BASE constants above + private static final String[][] CACHE_PROJECTIONS = { + Account.CONTENT_PROJECTION, + Mailbox.CONTENT_PROJECTION, + Message.CONTENT_PROJECTION, + null, // Attachment + HostAuth.CONTENT_PROJECTION, + null, // Updated message + null, // Deleted message + Policy.CONTENT_PROJECTION, null, // Quick response null // Body }; private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH); + private static final String MAILBOX_PRE_CACHE_SELECTION = MailboxColumns.TYPE + " IN (" + + Mailbox.TYPE_INBOX + "," + Mailbox.TYPE_DRAFTS + "," + Mailbox.TYPE_TRASH + "," + + Mailbox.TYPE_SENT + "," + Mailbox.TYPE_SEARCH + "," + Mailbox.TYPE_OUTBOX + ")"; + /** * Let's only generate these SQL strings once, as they are used frequently * Note that this isn't relevant for table creation strings, since they are used only once @@ -301,6 +332,7 @@ public class EmailProvider extends ContentProvider { // A specific account // insert into this URI causes a mailbox to be added to the account matcher.addURI(EmailContent.AUTHORITY, "account/#", ACCOUNT_ID); + matcher.addURI(EmailContent.AUTHORITY, "account/default", ACCOUNT_DEFAULT_ID); // Special URI to reset the new message count. Only update works, and content values // will be ignored. @@ -315,7 +347,8 @@ public class EmailProvider extends ContentProvider { // insert into this URI causes a message to be added to the mailbox // ** NOTE For now, the accountKey must be set manually in the values! matcher.addURI(EmailContent.AUTHORITY, "mailbox/#", MAILBOX_ID); - + matcher.addURI(EmailContent.AUTHORITY, "mailboxIdFromAccountAndType/#/#", + MAILBOX_ID_FROM_ACCOUNT_AND_TYPE); // All messages matcher.addURI(EmailContent.AUTHORITY, "message", MESSAGE); // A specific message @@ -383,7 +416,6 @@ public class EmailProvider extends ContentProvider { QUICK_RESPONSE_ACCOUNT_ID); } - /** * Wrap the UriMatcher call so we can throw a runtime exception if an unknown Uri is passed in * @param uri the Uri to match @@ -778,10 +810,80 @@ public class EmailProvider extends ContentProvider { // Check for any orphaned Messages in the updated/deleted tables deleteOrphans(mDatabase, Message.UPDATED_TABLE_NAME); deleteOrphans(mDatabase, Message.DELETED_TABLE_NAME); - + if (Email.DEBUG) { + Log.d(TAG, "EmailProvider pre-caching..."); + } + preCacheData(); + if (Email.DEBUG) { + Log.d(TAG, "Pre-caching finished."); + } return mDatabase; } + /** + * Pre-cache all of the items in a given table meeting the selection criteria + * @param tableUri the table uri + * @param baseProjection the base projection of that table + * @param selection the selection criteria + */ + private void preCacheTable(Uri tableUri, String[] baseProjection, String selection) { + Cursor c = query(tableUri, EmailContent.ID_PROJECTION, selection, null, null); + try { + while (c.moveToNext()) { + long id = c.getLong(EmailContent.ID_PROJECTION_COLUMN); + Cursor cachedCursor = query(ContentUris.withAppendedId( + tableUri, id), baseProjection, null, null, null); + if (cachedCursor != null) { + // For accounts, create a mailbox type map entry (if necessary) + if (tableUri == Account.CONTENT_URI) { + getOrCreateAccountMailboxTypeMap(id); + } + cachedCursor.close(); + } + } + } finally { + c.close(); + } + } + + private HashMap> mMailboxTypeMap = + new HashMap>(); + + private synchronized HashMap getOrCreateAccountMailboxTypeMap(long accountId) { + HashMap accountMailboxTypeMap = mMailboxTypeMap.get(accountId); + if (accountMailboxTypeMap == null) { + accountMailboxTypeMap = new HashMap(); + mMailboxTypeMap.put(accountId, accountMailboxTypeMap); + } + return accountMailboxTypeMap; + } + + private synchronized void addToMailboxTypeMap(Cursor c) { + long accountId = c.getLong(Mailbox.CONTENT_ACCOUNT_KEY_COLUMN); + int type = c.getInt(Mailbox.CONTENT_TYPE_COLUMN); + HashMap accountMailboxTypeMap = getOrCreateAccountMailboxTypeMap(accountId); + accountMailboxTypeMap.put(type, c.getLong(Mailbox.CONTENT_ID_COLUMN)); + } + + private void preCacheData() { + mMailboxTypeMap.clear(); + + // Pre-cache accounts, host auth's, policies, and special mailboxes + preCacheTable(Account.CONTENT_URI, Account.CONTENT_PROJECTION, null); + preCacheTable(HostAuth.CONTENT_URI, HostAuth.CONTENT_PROJECTION, null); + preCacheTable(Policy.CONTENT_URI, Policy.CONTENT_PROJECTION, null); + preCacheTable(Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION, MAILBOX_PRE_CACHE_SELECTION); + + // Create a map from account,type to a mailbox + Map snapshot = mCacheMailbox.getSnapshot(); + Collection values = snapshot.values(); + if (values != null) { + for (Cursor c: values) { + addToMailboxTypeMap(c); + } + } + } + /*package*/ static SQLiteDatabase getReadableDatabase(Context context) { DatabaseHelper helper = new DatabaseHelper(context, DATABASE_NAME); return helper.getReadableDatabase(); @@ -1175,7 +1277,7 @@ public class EmailProvider extends ContentProvider { boolean messageDeletion = false; ContentResolver resolver = context.getContentResolver(); - ContentCache cache = CONTENT_CACHES[table]; + ContentCache cache = mContentCaches[table]; String tableName = TABLE_NAMES[table]; int result = -1; @@ -1231,17 +1333,23 @@ public class EmailProvider extends ContentProvider { case ACCOUNT_ID: // Account deletion will clear all of the caches, as HostAuth's, // Mailboxes, and Messages will be deleted in the process - sCacheMailbox.invalidate("Delete", uri, selection); - sCacheHostAuth.invalidate("Delete", uri, selection); + mCacheMailbox.invalidate("Delete", uri, selection); + mCacheHostAuth.invalidate("Delete", uri, selection); + mCachePolicy.invalidate("Delete", uri, selection); //$FALL-THROUGH$ case MAILBOX_ID: // Mailbox deletion will clear the Message cache - sCacheMessage.invalidate("Delete", uri, selection); + mCacheMessage.invalidate("Delete", uri, selection); //$FALL-THROUGH$ case SYNCED_MESSAGE_ID: case MESSAGE_ID: case HOSTAUTH_ID: + case POLICY_ID: cache.invalidate("Delete", uri, selection); + // Make sure all data is properly cached + if (match != MESSAGE_ID) { + preCacheData(); + } break; } } @@ -1270,18 +1378,29 @@ public class EmailProvider extends ContentProvider { switch(match) { // See the comments above for deletion of ACCOUNT_ID, etc case ACCOUNT: - sCacheMailbox.invalidate("Delete", uri, selection); - sCacheHostAuth.invalidate("Delete", uri, selection); + mCacheMailbox.invalidate("Delete", uri, selection); + mCacheHostAuth.invalidate("Delete", uri, selection); + mCachePolicy.invalidate("Delete", uri, selection); //$FALL-THROUGH$ case MAILBOX: - sCacheMessage.invalidate("Delete", uri, selection); + mCacheMessage.invalidate("Delete", uri, selection); //$FALL-THROUGH$ case MESSAGE: case HOSTAUTH: + case POLICY: cache.invalidate("Delete", uri, selection); break; } result = db.delete(tableName, selection, selectionArgs); + switch(match) { + case ACCOUNT: + case MAILBOX: + case HOSTAUTH: + case POLICY: + // Make sure all data is properly cached + preCacheData(); + break; + } break; default: @@ -1384,9 +1503,11 @@ public class EmailProvider extends ContentProvider { try { switch (match) { - case MESSAGE: + // NOTE: It is NOT legal for production code to insert directly into UPDATED_MESSAGE + // or DELETED_MESSAGE; see the comment below for details case UPDATED_MESSAGE: case DELETED_MESSAGE: + case MESSAGE: case BODY: case ATTACHMENT: case MAILBOX: @@ -1396,6 +1517,33 @@ public class EmailProvider extends ContentProvider { case QUICK_RESPONSE: longId = db.insert(TABLE_NAMES[table], "foo", values); resultUri = ContentUris.withAppendedId(uri, longId); + switch(match) { + case MAILBOX: + if (values.containsKey(MailboxColumns.TYPE)) { + // Only cache special mailbox types + int type = values.getAsInteger(MailboxColumns.TYPE); + if (type != Mailbox.TYPE_INBOX && type != Mailbox.TYPE_OUTBOX && + type != Mailbox.TYPE_DRAFTS && type != Mailbox.TYPE_SENT && + type != Mailbox.TYPE_TRASH && type != Mailbox.TYPE_SEARCH) { + break; + } + } + //$FALL-THROUGH$ + case ACCOUNT: + case HOSTAUTH: + case POLICY: + // Cache new account, host auth, policy, and some mailbox rows + Cursor c = query(resultUri, CACHE_PROJECTIONS[table], null, null, null); + if (c != null) { + if (match == MAILBOX) { + addToMailboxTypeMap(c); + } else if (match == ACCOUNT) { + getOrCreateAccountMailboxTypeMap(longId); + } + c.close(); + } + break; + } // Clients shouldn't normally be adding rows to these tables, as they are // maintained by triggers. However, we need to be able to do this for unit // testing, so we allow the insert and then throw the same exception that we @@ -1529,7 +1677,7 @@ public class EmailProvider extends ContentProvider { String tableName = TABLE_NAMES[table]; // We can only use the cache if there's no selection if (selection == null) { - cache = CONTENT_CACHES[table]; + cache = mContentCaches[table]; } if (cache == null) { ContentCache.notCacheable(uri, selection); @@ -1537,6 +1685,37 @@ public class EmailProvider extends ContentProvider { try { switch (match) { + case ACCOUNT_DEFAULT_ID: + // Start with a snapshot of the cache + Map accountCache = mCacheAccount.getSnapshot(); + long accountId = Account.NO_ACCOUNT; + // Find the account with "isDefault" set + Collection accounts = accountCache.values(); + int numAccounts = accounts.size(); + for (Cursor accountCursor: accounts) { + if (accountCursor.getInt(Account.CONTENT_IS_DEFAULT_COLUMN) == 1 || + numAccounts == 1) { + accountId = accountCursor.getLong(Account.CONTENT_ID_COLUMN); + break; + } + } + // Return a cursor with an id projection + MatrixCursor mc = new MatrixCursor(EmailContent.ID_PROJECTION); + mc.addRow(new Object[] {accountId}); + return mc; + case MAILBOX_ID_FROM_ACCOUNT_AND_TYPE: + // Get accountId and type and find the mailbox in our map + accountId = Long.parseLong(uri.getPathSegments().get(1)); + int type = Integer.parseInt(uri.getPathSegments().get(2)); + HashMap accountMap = mMailboxTypeMap.get(accountId); + mc = new MatrixCursor(EmailContent.ID_PROJECTION); + Long mailboxId = null; + if (accountMap != null) { + mailboxId = accountMap.get(type); + } + // Return a cursor with an id projection + mc.addRow(new Object[] {mailboxId}); + return mc; case BODY: case MESSAGE: case UPDATED_MESSAGE: @@ -1547,6 +1726,17 @@ public class EmailProvider extends ContentProvider { case HOSTAUTH: case POLICY: case QUICK_RESPONSE: + // Special-case "count of accounts"; it's common and we always know it + if (match == ACCOUNT && Arrays.equals(projection, EmailContent.COUNT_COLUMNS) && + selection == null && limit.equals("1")) { + int accountCount = mMailboxTypeMap.size(); + // In the rare case there are MAX_CACHED_ACCOUNTS or more, we can't do this + if (accountCount < MAX_CACHED_ACCOUNTS) { + mc = new MatrixCursor(projection, 1); + mc.addRow(new Object[] {accountCount}); + return mc; + } + } c = db.query(tableName, projection, selection, selectionArgs, null, null, sortOrder, limit); break; @@ -1571,6 +1761,17 @@ public class EmailProvider extends ContentProvider { } c = db.query(tableName, projection, whereWithId(id, selection), selectionArgs, null, null, sortOrder, limit); + if (Email.DEBUG) { + switch(match) { + case ACCOUNT_ID: + case HOSTAUTH_ID: + case POLICY_ID: + case MAILBOX_ID: + Log.w(Logging.LOG_TAG, + "==== UNCACHED read of " + tableName + ", id = " + id); + break; + } + } if (cache != null) { c = cache.putCursor(c, id, projection, token); } @@ -1601,7 +1802,7 @@ public class EmailProvider extends ContentProvider { e.printStackTrace(); throw e; } finally { - if (cache != null && Email.DEBUG) { + if (cache != null && c != null && Email.DEBUG) { cache.recordQueryTime(c, System.nanoTime() - time); } } @@ -1820,11 +2021,12 @@ public class EmailProvider extends ContentProvider { values.remove(MailboxColumns.MESSAGE_COUNT); } - ContentCache cache = CONTENT_CACHES[table]; + ContentCache cache = mContentCaches[table]; String tableName = TABLE_NAMES[table]; String id = "0"; try { +outer: switch (match) { case MAILBOX_ID_ADD_TO_FIELD: case ACCOUNT_ID_ADD_TO_FIELD: @@ -1876,6 +2078,7 @@ public class EmailProvider extends ContentProvider { case ACCOUNT_ID: case HOSTAUTH_ID: case QUICK_RESPONSE_ID: + case POLICY_ID: id = uri.getPathSegments().get(1); if (cache != null) { cache.lock(id); @@ -1916,18 +2119,41 @@ public class EmailProvider extends ContentProvider { case MAILBOX: case ACCOUNT: case HOSTAUTH: + case POLICY: switch(match) { - case MESSAGE: + // To avoid invalidating the cache on updates, we execute them one at a + // time using the XXX_ID uri; these are all executed atomically case ACCOUNT: case MAILBOX: case HOSTAUTH: + case POLICY: + Cursor c = db.query(tableName, EmailContent.ID_PROJECTION, + selection, selectionArgs, null, null, null); + db.beginTransaction(); + result = 0; + try { + while (c.moveToNext()) { + update(ContentUris.withAppendedId( + uri, c.getLong(EmailContent.ID_PROJECTION_COLUMN)), + values, null, null); + result++; + } + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + c.close(); + } + break outer; + // Any cached table other than those above should be invalidated here + case MESSAGE: // If we're doing some generic update, the whole cache needs to be // invalidated. This case should be quite rare cache.invalidate("Update", uri, selection); - break; + //$FALL-THROUGH$ + default: + result = db.update(tableName, values, selection, selectionArgs); + break outer; } - result = db.update(tableName, values, selection, selectionArgs); - break; case ACCOUNT_RESET_NEW_COUNT_ID: id = uri.getPathSegments().get(1); if (cache != null) { @@ -2105,6 +2331,7 @@ public class EmailProvider extends ContentProvider { // Shouldn't be needed unless we're debugging and interrupt the process Log.w(TAG, "Exception upgrading EmailProvider.db from 17 to 18 " + e); } + ContentCache.invalidateAllCaches(); } /** Upgrades the database from v20 to v21 */ @@ -2206,4 +2433,20 @@ public class EmailProvider extends ContentProvider { Log.w(TAG, "Exception upgrading EmailProvider.db from 24 to 25 " + e); } } + + /** + * For testing purposes, check whether a given row is cached + * @param baseUri the base uri of the EmailContent + * @param id the row id of the EmailContent + * @return whether or not the row is currently cached + */ + @VisibleForTesting + protected boolean isCached(Uri baseUri, long id) { + int match = findMatch(baseUri, "isCached"); + int table = match >> BASE_SHIFT; + ContentCache cache = mContentCaches[table]; + if (cache == null) return false; + Cursor cc = cache.get(Long.toString(id)); + return (cc != null); + } } diff --git a/tests/src/com/android/email/ControllerProviderOpsTests.java b/tests/src/com/android/email/ControllerProviderOpsTests.java index a027c7ccd..229e34137 100644 --- a/tests/src/com/android/email/ControllerProviderOpsTests.java +++ b/tests/src/com/android/email/ControllerProviderOpsTests.java @@ -61,7 +61,7 @@ public class ControllerProviderOpsTests extends ProviderTestCase2 mContext = getContext(); mTestController = new TestController(mProviderContext, mContext); // Invalidate all caches, since we reset the database for each test - ContentCache.invalidateAllCachesForTest(); + ContentCache.invalidateAllCaches(); } @Override diff --git a/tests/src/com/android/email/DBTestHelper.java b/tests/src/com/android/email/DBTestHelper.java index 029ff7384..0ba3eaa45 100644 --- a/tests/src/com/android/email/DBTestHelper.java +++ b/tests/src/com/android/email/DBTestHelper.java @@ -240,7 +240,7 @@ public final class DBTestHelper { ap.attachInfo(providerContext, null); resolver.addProvider(AttachmentUtilities.AUTHORITY, ap); - ContentCache.invalidateAllCachesForTest(); + ContentCache.invalidateAllCaches(); return providerContext; } diff --git a/tests/src/com/android/email/SecurityPolicyTests.java b/tests/src/com/android/email/SecurityPolicyTests.java index e06967e7a..bf95fb504 100644 --- a/tests/src/com/android/email/SecurityPolicyTests.java +++ b/tests/src/com/android/email/SecurityPolicyTests.java @@ -57,7 +57,7 @@ public class SecurityPolicyTests extends ProviderTestCase2 { super.setUp(); mMockContext = new MockContext2(getMockContext(), mContext); // Invalidate all caches, since we reset the database for each test - ContentCache.invalidateAllCachesForTest(); + ContentCache.invalidateAllCaches(); } /** diff --git a/tests/src/com/android/email/activity/MailboxFinderTest.java b/tests/src/com/android/email/activity/MailboxFinderTest.java index ba1ee599f..6087081db 100644 --- a/tests/src/com/android/email/activity/MailboxFinderTest.java +++ b/tests/src/com/android/email/activity/MailboxFinderTest.java @@ -16,6 +16,11 @@ package com.android.email.activity; +import android.content.Context; +import android.test.InstrumentationTestCase; +import android.test.ProviderTestCase2; +import android.test.suitebuilder.annotation.LargeTest; + import com.android.email.Controller; import com.android.email.DBTestHelper; import com.android.email.Email; @@ -25,11 +30,6 @@ import com.android.emailcommon.mail.MessagingException; import com.android.emailcommon.provider.Account; import com.android.emailcommon.provider.Mailbox; -import android.content.Context; -import android.test.InstrumentationTestCase; -import android.test.ProviderTestCase2; -import android.test.suitebuilder.annotation.LargeTest; - /** * Test case for {@link MailboxFinder}. * diff --git a/tests/src/com/android/email/activity/MessageComposeTests.java b/tests/src/com/android/email/activity/MessageComposeTests.java index 7d5eee30b..17d0aadd7 100644 --- a/tests/src/com/android/email/activity/MessageComposeTests.java +++ b/tests/src/com/android/email/activity/MessageComposeTests.java @@ -16,18 +16,6 @@ package com.android.email.activity; -import com.android.email.Email; -import com.android.email.EmailAddressValidator; -import com.android.email.R; -import com.android.email.TestUtils; -import com.android.emailcommon.Logging; -import com.android.emailcommon.mail.Address; -import com.android.emailcommon.mail.MessagingException; -import com.android.emailcommon.provider.Account; -import com.android.emailcommon.provider.EmailContent.Attachment; -import com.android.emailcommon.provider.EmailContent.Message; -import com.google.android.collect.Lists; - import android.content.ContentUris; import android.content.Context; import android.content.Intent; @@ -41,6 +29,18 @@ import android.view.View; import android.widget.EditText; import android.widget.MultiAutoCompleteTextView; +import com.android.email.Email; +import com.android.email.EmailAddressValidator; +import com.android.email.R; +import com.android.email.TestUtils; +import com.android.emailcommon.Logging; +import com.android.emailcommon.mail.Address; +import com.android.emailcommon.mail.MessagingException; +import com.android.emailcommon.provider.Account; +import com.android.emailcommon.provider.EmailContent.Attachment; +import com.android.emailcommon.provider.EmailContent.Message; +import com.google.android.collect.Lists; + import java.util.ArrayList; diff --git a/tests/src/com/android/email/activity/RecentMailboxManagerTest.java b/tests/src/com/android/email/activity/RecentMailboxManagerTest.java index fbc60b878..38c177f6c 100644 --- a/tests/src/com/android/email/activity/RecentMailboxManagerTest.java +++ b/tests/src/com/android/email/activity/RecentMailboxManagerTest.java @@ -16,15 +16,15 @@ package com.android.email.activity; +import android.content.Context; +import android.test.AndroidTestCase; + import com.android.email.DBTestHelper; import com.android.email.MockClock; import com.android.email.provider.ContentCache; import com.android.email.provider.ProviderTestUtils; import com.android.emailcommon.provider.Mailbox; -import android.content.Context; -import android.test.AndroidTestCase; - import java.util.ArrayList; /** @@ -64,6 +64,8 @@ public class RecentMailboxManagerTest extends AndroidTestCase { ProviderTestUtils.setupMailbox("laurel", 1L, true, mMockContext, Mailbox.TYPE_MAIL), ProviderTestUtils.setupMailbox("hardy", 1L, true, mMockContext, Mailbox.TYPE_MAIL), }; + // Invalidate all caches, since we reset the database for each test + ContentCache.invalidateAllCaches(); } @Override diff --git a/tests/src/com/android/email/provider/PolicyTests.java b/tests/src/com/android/email/provider/PolicyTests.java index a8f7f3b4e..2903c45b4 100644 --- a/tests/src/com/android/email/provider/PolicyTests.java +++ b/tests/src/com/android/email/provider/PolicyTests.java @@ -55,7 +55,7 @@ public class PolicyTests extends ProviderTestCase2 { super.setUp(); mMockContext = getMockContext(); // Invalidate all caches, since we reset the database for each test - ContentCache.invalidateAllCachesForTest(); + ContentCache.invalidateAllCaches(); } @Override diff --git a/tests/src/com/android/email/provider/ProviderTests.java b/tests/src/com/android/email/provider/ProviderTests.java index 2a013ec2e..d34bbbb93 100644 --- a/tests/src/com/android/email/provider/ProviderTests.java +++ b/tests/src/com/android/email/provider/ProviderTests.java @@ -16,23 +16,6 @@ package com.android.email.provider; -import com.android.emailcommon.AccountManagerTypes; -import com.android.emailcommon.provider.Account; -import com.android.emailcommon.provider.EmailContent; -import com.android.emailcommon.provider.EmailContent.AccountColumns; -import com.android.emailcommon.provider.EmailContent.Attachment; -import com.android.emailcommon.provider.EmailContent.AttachmentColumns; -import com.android.emailcommon.provider.EmailContent.Body; -import com.android.emailcommon.provider.EmailContent.BodyColumns; -import com.android.emailcommon.provider.EmailContent.MailboxColumns; -import com.android.emailcommon.provider.EmailContent.Message; -import com.android.emailcommon.provider.EmailContent.MessageColumns; -import com.android.emailcommon.provider.HostAuth; -import com.android.emailcommon.provider.Mailbox; -import com.android.emailcommon.utility.AccountReconciler; -import com.android.emailcommon.utility.TextUtilities; -import com.android.emailcommon.utility.Utility; - import android.accounts.AccountManager; import android.accounts.AuthenticatorException; import android.accounts.OperationCanceledException; @@ -49,6 +32,24 @@ import android.os.Parcel; import android.test.MoreAsserts; import android.test.ProviderTestCase2; +import com.android.emailcommon.AccountManagerTypes; +import com.android.emailcommon.provider.Account; +import com.android.emailcommon.provider.EmailContent; +import com.android.emailcommon.provider.EmailContent.AccountColumns; +import com.android.emailcommon.provider.EmailContent.Attachment; +import com.android.emailcommon.provider.EmailContent.AttachmentColumns; +import com.android.emailcommon.provider.EmailContent.Body; +import com.android.emailcommon.provider.EmailContent.BodyColumns; +import com.android.emailcommon.provider.EmailContent.MailboxColumns; +import com.android.emailcommon.provider.EmailContent.Message; +import com.android.emailcommon.provider.EmailContent.MessageColumns; +import com.android.emailcommon.provider.HostAuth; +import com.android.emailcommon.provider.Mailbox; +import com.android.emailcommon.provider.Policy; +import com.android.emailcommon.utility.AccountReconciler; +import com.android.emailcommon.utility.TextUtilities; +import com.android.emailcommon.utility.Utility; + import java.io.File; import java.io.IOException; import java.util.ArrayList; @@ -81,7 +82,7 @@ public class ProviderTests extends ProviderTestCase2 { mMockContext = getMockContext(); mProvider = getProvider(); // Invalidate all caches, since we reset the database for each test - ContentCache.invalidateAllCachesForTest(); + ContentCache.invalidateAllCaches(); } @Override @@ -1737,9 +1738,11 @@ public class ProviderTests extends ProviderTestCase2 { public void testClearAccountHoldFlags() { Account a1 = ProviderTestUtils.setupAccount("holdflag-1", false, mMockContext); a1.mFlags = Account.FLAGS_NOTIFY_NEW_MAIL; + a1.mPolicy = new Policy(); a1.save(mMockContext); Account a2 = ProviderTestUtils.setupAccount("holdflag-2", false, mMockContext); a2.mFlags = Account.FLAGS_VIBRATE_ALWAYS | Account.FLAGS_SECURITY_HOLD; + a2.mPolicy = new Policy(); a2.save(mMockContext); // bulk clear @@ -2120,7 +2123,7 @@ public class ProviderTests extends ProviderTestCase2 { public void testUpgradeFromVersion17ToVersion18() { final Context c = mMockContext; // Create accounts - Account a1 =createAccount(c, "exchange", + Account a1 = createAccount(c, "exchange", ProviderTestUtils.setupHostAuth("eas", "exchange.host.com", true, c), null); Account a2 = createAccount(c, "imap", @@ -2255,6 +2258,109 @@ public class ProviderTests extends ProviderTestCase2 { return false; } + public void testAutoCacheNewContent() { + Account account = ProviderTestUtils.setupAccount("account-hostauth", false, mMockContext); + // add hostauth data, which should be saved the first time + account.mHostAuthRecv = ProviderTestUtils.setupHostAuth("account-hostauth-recv", -1, false, + mMockContext); + account.mHostAuthSend = ProviderTestUtils.setupHostAuth("account-hostauth-send", -1, false, + mMockContext); + account.save(mMockContext); + assertTrue(mProvider.isCached(Account.CONTENT_URI, account.mId)); + assertTrue(mProvider.isCached(HostAuth.CONTENT_URI, account.mHostAuthRecv.mId)); + assertTrue(mProvider.isCached(HostAuth.CONTENT_URI, account.mHostAuthSend.mId)); + } + + /** Creates a mailbox; redefine as we need version 17 mailbox values */ + private Mailbox createTypeMailbox(Context c, long accountId, int type) { + Mailbox box = new Mailbox(); + + box.mDisplayName = "foo"; + box.mServerId = "1:1"; + box.mParentKey = 0; + box.mAccountKey = accountId; + // Don't care about the fields below ... set them for giggles + box.mType = type; + box.save(c); + return box; + } + + public void testAutoCacheInvalidate() { + // Create 3 accounts with hostauth and 3 mailboxes each (2 of which are pre-cached) + Account a = ProviderTestUtils.setupAccount("account1", false, mMockContext); + a.mHostAuthRecv = ProviderTestUtils.setupHostAuth("account-recv", -1, false, + mMockContext); + a.mHostAuthSend = ProviderTestUtils.setupHostAuth("account-send", -1, false, + mMockContext); + a.save(mMockContext); + Mailbox a1 = createTypeMailbox(mMockContext, a.mId, Mailbox.TYPE_INBOX); + Mailbox a2 = createTypeMailbox(mMockContext, a.mId, Mailbox.TYPE_MAIL); + Mailbox a3 = createTypeMailbox(mMockContext, a.mId, Mailbox.TYPE_DRAFTS); + Account b = ProviderTestUtils.setupAccount("account2", false, mMockContext); + b.mHostAuthRecv = ProviderTestUtils.setupHostAuth("account-recv", -1, false, + mMockContext); + b.mHostAuthSend = ProviderTestUtils.setupHostAuth("accoun-send", -1, false, + mMockContext); + b.save(mMockContext); + Mailbox b1 = createTypeMailbox(mMockContext, b.mId, Mailbox.TYPE_OUTBOX); + Mailbox b2 = createTypeMailbox(mMockContext, b.mId, Mailbox.TYPE_MAIL); + Mailbox b3 = createTypeMailbox(mMockContext, b.mId, Mailbox.TYPE_SENT); + Account c = ProviderTestUtils.setupAccount("account3", false, mMockContext); + c.mHostAuthRecv = ProviderTestUtils.setupHostAuth("account-recv", -1, false, + mMockContext); + c.mHostAuthSend = ProviderTestUtils.setupHostAuth("account-send", -1, false, + mMockContext); + c.save(mMockContext); + Mailbox c1 = createTypeMailbox(mMockContext, c.mId, Mailbox.TYPE_SEARCH); + Mailbox c2 = createTypeMailbox(mMockContext, c.mId, Mailbox.TYPE_MAIL); + Mailbox c3 = createTypeMailbox(mMockContext, c.mId, Mailbox.TYPE_TRASH); + + // Confirm expected cache state + assertTrue(mProvider.isCached(Account.CONTENT_URI, a.mId)); + assertTrue(mProvider.isCached(HostAuth.CONTENT_URI, a.mHostAuthRecv.mId)); + assertTrue(mProvider.isCached(HostAuth.CONTENT_URI, a.mHostAuthSend.mId)); + assertTrue(mProvider.isCached(Account.CONTENT_URI, b.mId)); + assertTrue(mProvider.isCached(HostAuth.CONTENT_URI, b.mHostAuthRecv.mId)); + assertTrue(mProvider.isCached(HostAuth.CONTENT_URI, b.mHostAuthSend.mId)); + assertTrue(mProvider.isCached(Account.CONTENT_URI, c.mId)); + assertTrue(mProvider.isCached(HostAuth.CONTENT_URI, c.mHostAuthRecv.mId)); + assertTrue(mProvider.isCached(HostAuth.CONTENT_URI, c.mHostAuthSend.mId)); + + assertTrue(mProvider.isCached(Mailbox.CONTENT_URI, a1.mId)); + assertFalse(mProvider.isCached(Mailbox.CONTENT_URI, a2.mId)); + assertTrue(mProvider.isCached(Mailbox.CONTENT_URI, a3.mId)); + assertTrue(mProvider.isCached(Mailbox.CONTENT_URI, b1.mId)); + assertFalse(mProvider.isCached(Mailbox.CONTENT_URI, b2.mId)); + assertTrue(mProvider.isCached(Mailbox.CONTENT_URI, b3.mId)); + assertTrue(mProvider.isCached(Mailbox.CONTENT_URI, c1.mId)); + assertFalse(mProvider.isCached(Mailbox.CONTENT_URI, c2.mId)); + assertTrue(mProvider.isCached(Mailbox.CONTENT_URI, c3.mId)); + + // Delete account b + EmailContent.delete(mMockContext, Account.CONTENT_URI, b.mId); + + // Confirm cache state + assertTrue(mProvider.isCached(Account.CONTENT_URI, a.mId)); + assertTrue(mProvider.isCached(HostAuth.CONTENT_URI, a.mHostAuthRecv.mId)); + assertTrue(mProvider.isCached(HostAuth.CONTENT_URI, a.mHostAuthSend.mId)); + assertFalse(mProvider.isCached(Account.CONTENT_URI, b.mId)); + assertFalse(mProvider.isCached(HostAuth.CONTENT_URI, b.mHostAuthRecv.mId)); + assertFalse(mProvider.isCached(HostAuth.CONTENT_URI, b.mHostAuthSend.mId)); + assertTrue(mProvider.isCached(Account.CONTENT_URI, c.mId)); + assertTrue(mProvider.isCached(HostAuth.CONTENT_URI, c.mHostAuthRecv.mId)); + assertTrue(mProvider.isCached(HostAuth.CONTENT_URI, c.mHostAuthSend.mId)); + + assertTrue(mProvider.isCached(Mailbox.CONTENT_URI, a1.mId)); + assertFalse(mProvider.isCached(Mailbox.CONTENT_URI, a2.mId)); + assertTrue(mProvider.isCached(Mailbox.CONTENT_URI, a3.mId)); + assertFalse(mProvider.isCached(Mailbox.CONTENT_URI, b1.mId)); + assertFalse(mProvider.isCached(Mailbox.CONTENT_URI, b2.mId)); + assertFalse(mProvider.isCached(Mailbox.CONTENT_URI, b3.mId)); + assertTrue(mProvider.isCached(Mailbox.CONTENT_URI, c1.mId)); + assertFalse(mProvider.isCached(Mailbox.CONTENT_URI, c2.mId)); + assertTrue(mProvider.isCached(Mailbox.CONTENT_URI, c3.mId)); + } + /** * Remove a single pop/imap account from the AccountManager * @param accountManager our AccountManager diff --git a/tests/src/com/android/emailcommon/provider/MailboxTests.java b/tests/src/com/android/emailcommon/provider/MailboxTests.java index 8a6dec486..573e3d83a 100644 --- a/tests/src/com/android/emailcommon/provider/MailboxTests.java +++ b/tests/src/com/android/emailcommon/provider/MailboxTests.java @@ -60,7 +60,7 @@ public class MailboxTests extends ProviderTestCase2 { mMockContext = getMockContext(); mProvider = getProvider(); // Invalidate all caches, since we reset the database for each test - ContentCache.invalidateAllCachesForTest(); + ContentCache.invalidateAllCaches(); } ////////////////////////////////////////////////////////// diff --git a/tests/src/com/android/emailcommon/provider/QuickResponseTests.java b/tests/src/com/android/emailcommon/provider/QuickResponseTests.java index b9258104f..f94136d78 100644 --- a/tests/src/com/android/emailcommon/provider/QuickResponseTests.java +++ b/tests/src/com/android/emailcommon/provider/QuickResponseTests.java @@ -16,23 +16,13 @@ package com.android.emailcommon.provider; -import com.android.email.provider.ContentCache; -import com.android.email.provider.EmailProvider; -import com.android.email.provider.ProviderTestUtils; -import com.android.emailcommon.provider.QuickResponse; -import com.android.emailcommon.utility.Utility; - -import android.content.ContentUris; -import android.content.ContentValues; import android.content.Context; -import android.net.Uri; import android.os.Parcel; -import android.test.MoreAsserts; import android.test.ProviderTestCase2; import android.test.suitebuilder.annotation.SmallTest; -import android.util.Log; -import java.util.Arrays; +import com.android.email.provider.ContentCache; +import com.android.email.provider.EmailProvider; /** * Unit tests for the QuickResponse class @@ -52,7 +42,7 @@ public class QuickResponseTests extends ProviderTestCase2 { mMockContext = getMockContext(); mProvider = getProvider(); // Invalidate all caches, since we reset the database for each test - ContentCache.invalidateAllCachesForTest(); + ContentCache.invalidateAllCaches(); } public void testParcelling() {