Support making a "snapshot" of databases during tests.

- We normally use in-memory DBs in unit tests because that's faster.
Add a flag to switch to file-based DBs so we can copy them.

- Also cleaned up the getDatabaseHelper() methods -- we had two,
one with no arguments and the other without, and it wasn't clear
which ones should be used when.  Now the one that takes a context
is renamed to make it clear the distinction.

Test: run-all-tests.sh with and without the flag set.

Change-Id: I376674a94e35a3314d91813debbeee5b3814f4a9
diff --git a/src/com/android/providers/contacts/AbstractContactsProvider.java b/src/com/android/providers/contacts/AbstractContactsProvider.java
index 0e67d10..1bc59e7 100644
--- a/src/com/android/providers/contacts/AbstractContactsProvider.java
+++ b/src/com/android/providers/contacts/AbstractContactsProvider.java
@@ -84,7 +84,7 @@
     /**
      * The DB helper to use for this content provider.
      */
-    private SQLiteOpenHelper mDbHelper;
+    private ContactsDatabaseHelper mDbHelper;
 
     /**
      * The database helper to serialize all transactions on.  If non-null, any new transaction
@@ -133,12 +133,12 @@
     @Override
     public boolean onCreate() {
         Context context = getContext();
-        mDbHelper = getDatabaseHelper(context);
+        mDbHelper = newDatabaseHelper(context);
         mTransactionHolder = getTransactionHolder();
         return true;
     }
 
-    public SQLiteOpenHelper getDatabaseHelper() {
+    public ContactsDatabaseHelper getDatabaseHelper() {
         return mDbHelper;
     }
 
@@ -345,8 +345,9 @@
 
     /**
      * Gets the database helper for this contacts provider.  This is called once, during onCreate().
+     * Do not call in other places.
      */
-    protected abstract SQLiteOpenHelper getDatabaseHelper(Context context);
+    protected abstract ContactsDatabaseHelper newDatabaseHelper(Context context);
 
     /**
      * Gets the thread-local transaction holder to use for keeping track of the transaction.  This
diff --git a/src/com/android/providers/contacts/ContactsDatabaseHelper.java b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
index 394a085..83f6195 100644
--- a/src/com/android/providers/contacts/ContactsDatabaseHelper.java
+++ b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
@@ -1032,8 +1032,8 @@
      * Returns a new instance for unit tests.
      */
     @NeededForTesting
-    public static ContactsDatabaseHelper getNewInstanceForTest(Context context) {
-        return new ContactsDatabaseHelper(context, null, false, /* isTestInstance=*/ true);
+    public static ContactsDatabaseHelper getNewInstanceForTest(Context context, String filename) {
+        return new ContactsDatabaseHelper(context, filename, false, /* isTestInstance=*/ true);
     }
 
     protected ContactsDatabaseHelper(
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index 0000570..583bedc 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -1582,7 +1582,7 @@
         mMetadataSyncEnabled = android.provider.Settings.Global.getInt(
                 getContext().getContentResolver(), Global.CONTACT_METADATA_SYNC_ENABLED, 0) == 1;
 
-        mContactsHelper = getDatabaseHelper(getContext());
+        mContactsHelper = getDatabaseHelper();
         mDbHelper.set(mContactsHelper);
 
         // Set up the DB helper for keeping transactions serialized.
@@ -1611,7 +1611,7 @@
         ProviderInfo profileInfo = new ProviderInfo();
         profileInfo.authority = ContactsContract.AUTHORITY;
         mProfileProvider.attachInfo(getContext(), profileInfo);
-        mProfileHelper = mProfileProvider.getDatabaseHelper(getContext());
+        mProfileHelper = mProfileProvider.getDatabaseHelper();
         mEnterprisePolicyGuard = new EnterprisePolicyGuard(getContext());
 
         // Initialize the pre-authorized URI duration.
@@ -2060,7 +2060,7 @@
     }
 
     @Override
-    public ContactsDatabaseHelper getDatabaseHelper(final Context context) {
+    public ContactsDatabaseHelper newDatabaseHelper(final Context context) {
         return ContactsDatabaseHelper.getInstance(context);
     }
 
diff --git a/src/com/android/providers/contacts/ProfileDatabaseHelper.java b/src/com/android/providers/contacts/ProfileDatabaseHelper.java
index 5628091..4d605f2 100644
--- a/src/com/android/providers/contacts/ProfileDatabaseHelper.java
+++ b/src/com/android/providers/contacts/ProfileDatabaseHelper.java
@@ -42,8 +42,8 @@
      * Returns a new instance for unit tests.
      */
     @NeededForTesting
-    public static ProfileDatabaseHelper getNewInstanceForTest(Context context) {
-        return new ProfileDatabaseHelper(context, null, false, /* isTestInstance=*/ true);
+    public static ProfileDatabaseHelper getNewInstanceForTest(Context context, String filename) {
+        return new ProfileDatabaseHelper(context, filename, false, /* isTestInstance=*/ true);
     }
 
     private ProfileDatabaseHelper(
diff --git a/src/com/android/providers/contacts/ProfileProvider.java b/src/com/android/providers/contacts/ProfileProvider.java
index 88ae4c3..73d1215 100644
--- a/src/com/android/providers/contacts/ProfileProvider.java
+++ b/src/com/android/providers/contacts/ProfileProvider.java
@@ -48,10 +48,14 @@
     }
 
     @Override
-    protected ProfileDatabaseHelper getDatabaseHelper(Context context) {
+    protected ProfileDatabaseHelper newDatabaseHelper(Context context) {
         return ProfileDatabaseHelper.getInstance(context);
     }
 
+    public ProfileDatabaseHelper getDatabaseHelper() {
+        return (ProfileDatabaseHelper) super.getDatabaseHelper();
+    }
+
     @Override
     protected ThreadLocal<ContactsTransaction> getTransactionHolder() {
         return mDelegate.getTransactionHolder();
diff --git a/tests/src/com/android/providers/contacts/ContactMetadataProviderTest.java b/tests/src/com/android/providers/contacts/ContactMetadataProviderTest.java
index 1d0d24a..3d8b8eb 100644
--- a/tests/src/com/android/providers/contacts/ContactMetadataProviderTest.java
+++ b/tests/src/com/android/providers/contacts/ContactMetadataProviderTest.java
@@ -103,7 +103,7 @@
         // Reset the dbHelper to be the one ContactsProvider2 is using. Before this, two providers
         // are using different dbHelpers.
         mContactMetadataProvider.setDatabaseHelper(((SynchronousContactsProvider2)
-                mActor.provider).getDatabaseHelper(getContext()));
+                mActor.provider).getDatabaseHelper());
         setupData();
     }
 
diff --git a/tests/src/com/android/providers/contacts/ContactsActor.java b/tests/src/com/android/providers/contacts/ContactsActor.java
index d8c0a09..6927bf2 100644
--- a/tests/src/com/android/providers/contacts/ContactsActor.java
+++ b/tests/src/com/android/providers/contacts/ContactsActor.java
@@ -58,7 +58,6 @@
 import android.provider.ContactsContract.RawContacts;
 import android.provider.ContactsContract.StatusUpdates;
 import android.test.IsolatedContext;
-import android.test.RenamingDelegatingContext;
 import android.test.mock.MockContentResolver;
 import android.test.mock.MockContext;
 
@@ -370,7 +369,11 @@
 
     public <T extends ContentProvider> T addProvider(Class<T> providerClass,
             String authority, Context providerContext) throws Exception {
-        T provider = providerClass.newInstance();
+        return addProvider(providerClass.newInstance(), authority, providerContext);
+    }
+
+    public <T extends ContentProvider> T addProvider(T provider,
+            String authority, Context providerContext) throws Exception {
         ProviderInfo info = new ProviderInfo();
 
         // Here, authority can have "user-id@".  We want to use it for addProvider, but provider
diff --git a/tests/src/com/android/providers/contacts/ContactsDatabaseHelperTest.java b/tests/src/com/android/providers/contacts/ContactsDatabaseHelperTest.java
index 10cd2fb..be3a73b 100644
--- a/tests/src/com/android/providers/contacts/ContactsDatabaseHelperTest.java
+++ b/tests/src/com/android/providers/contacts/ContactsDatabaseHelperTest.java
@@ -41,7 +41,7 @@
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-        mDbHelper = getContactsProvider().getDatabaseHelper(getContext());
+        mDbHelper = getContactsProvider().getDatabaseHelper();
         mDb = mDbHelper.getWritableDatabase();
     }
 
diff --git a/tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java b/tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java
index 4db79f2..d543b7e 100644
--- a/tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java
+++ b/tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java
@@ -59,6 +59,7 @@
 import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns;
 import com.android.providers.contacts.ContactsDatabaseHelper.StatusUpdatesColumns;
 import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
+import com.android.providers.contacts.testutil.TestUtil;
 import com.android.providers.contacts.util.PropertyUtils;
 
 import junit.framework.AssertionFailedError;
@@ -89,7 +90,8 @@
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-        mHelper = ContactsDatabaseHelper.getNewInstanceForTest(getContext());
+        mHelper = ContactsDatabaseHelper.getNewInstanceForTest(getContext(),
+                TestUtils.getContactsDatabaseFilename(getContext()));
         mHelper.onConfigure(mDb);
     }
 
diff --git a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
index a053974..f951f6d 100644
--- a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
@@ -1173,7 +1173,7 @@
         Uri dataUri = mResolver.insert(Data.CONTENT_URI, values);
 
         final ContactsProvider2 cp = (ContactsProvider2) getProvider();
-        final ContactsDatabaseHelper helper = cp.getDatabaseHelper(mContext);
+        final ContactsDatabaseHelper helper = cp.getDatabaseHelper();
         String data1 = values.getAsString(Data.DATA1);
         String data2 = values.getAsString(Data.DATA2);
         String combineString = data1+data2;
@@ -1234,7 +1234,7 @@
 
         // Check for photo data's hashId is correct or not.
         final ContactsProvider2 cp = (ContactsProvider2) getProvider();
-        final ContactsDatabaseHelper helper = cp.getDatabaseHelper(mContext);
+        final ContactsDatabaseHelper helper = cp.getDatabaseHelper();
         String hashId = helper.getPhotoHashId();
         assertStoredValue(dataUri, Data.HASH_ID, hashId);
 
@@ -2067,10 +2067,11 @@
 
         // Note here we use a standalone CP2 so it'll have its own db helper.
         // Also use AlteringUserContext here to report the corp user id.
+        final int userId = MockUserManager.CORP_USER.id;
         SynchronousContactsProvider2 provider = mActor.addProvider(
-                StandaloneContactsProvider2.class,
-                "" + MockUserManager.CORP_USER.id + "@com.android.contacts",
-                new AlteringUserContext(mActor.getProviderContext(), MockUserManager.CORP_USER.id));
+                new SecondaryUserContactsProvider2(userId),
+                "" + userId + "@com.android.contacts",
+                new AlteringUserContext(mActor.getProviderContext(), userId));
         provider.wipeData();
         return provider;
     }
@@ -3046,7 +3047,7 @@
         // Reset the dbHelper to be the one ContactsProvider2 is using. Before this, two providers
         // are using different dbHelpers.
         contactMetadataProvider.setDatabaseHelper(((SynchronousContactsProvider2)
-                mActor.provider).getDatabaseHelper(getContext()));
+                mActor.provider).getDatabaseHelper());
         // Create an account first.
         String backupId = "backupId001";
         String accountType = "accountType";
@@ -3102,7 +3103,7 @@
         // Reset the dbHelper to be the one ContactsProvider2 is using. Before this, two providers
         // are using different dbHelpers.
         contactMetadataProvider.setDatabaseHelper(((SynchronousContactsProvider2)
-                mActor.provider).getDatabaseHelper(getContext()));
+                mActor.provider).getDatabaseHelper());
         // Create an account first.
         String backupId = "backupId001";
         String accountType = "accountType";
@@ -3168,7 +3169,7 @@
         // Reset the dbHelper to be the one ContactsProvider2 is using. Before this, two providers
         // are using different dbHelpers.
         contactMetadataProvider.setDatabaseHelper(((SynchronousContactsProvider2)
-                mActor.provider).getDatabaseHelper(getContext()));
+                mActor.provider).getDatabaseHelper());
         // Enable metadataSync flag.
         final ContactsProvider2 cp = (ContactsProvider2) getProvider();
         cp.setMetadataSyncForTest(true);
@@ -6792,7 +6793,7 @@
         // Reset the dbHelper to be the one ContactsProvider2 is using. Before this, two providers
         // are using different dbHelpers.
         contactMetadataProvider.setDatabaseHelper(((SynchronousContactsProvider2)
-                mActor.provider).getDatabaseHelper(getContext()));
+                mActor.provider).getDatabaseHelper());
 
         // Create a doomed metadata.
         String backupId = "backupIdForDoomed";
diff --git a/tests/src/com/android/providers/contacts/PhotoStoreTest.java b/tests/src/com/android/providers/contacts/PhotoStoreTest.java
index 4e797f7..8ba0438 100644
--- a/tests/src/com/android/providers/contacts/PhotoStoreTest.java
+++ b/tests/src/com/android/providers/contacts/PhotoStoreTest.java
@@ -56,7 +56,7 @@
         mProvider = ((SynchronousContactsProvider2) mActor.provider);
         mPhotoStore = mProvider.getPhotoStore();
         mProvider.wipeData();
-        mDb = mProvider.getDatabaseHelper(getContext()).getReadableDatabase();
+        mDb = mProvider.getDatabaseHelper().getReadableDatabase();
     }
 
     @Override
diff --git a/tests/src/com/android/providers/contacts/RenamingDelegatingContext.java b/tests/src/com/android/providers/contacts/RenamingDelegatingContext.java
new file mode 100644
index 0000000..260b730
--- /dev/null
+++ b/tests/src/com/android/providers/contacts/RenamingDelegatingContext.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2007 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.providers.contacts;
+
+import android.content.ContentProvider;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.database.DatabaseErrorHandler;
+import android.database.sqlite.SQLiteDatabase;
+import android.os.FileUtils;
+import android.util.Log;
+
+import com.google.android.collect.Sets;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.util.Set;
+
+/**
+ * This file was copied from framework/base.  The DB related file names now understand fullpath
+ * filenames and will not append the prefix for them.
+ */
+public class RenamingDelegatingContext extends ContextWrapper {
+
+    private Context mFileContext;
+    private String mFilePrefix = null;
+    private File mCacheDir;
+    private final Object mSync = new Object();
+
+    private Set<String> mDatabaseNames = Sets.newHashSet();
+    private Set<String> mFileNames = Sets.newHashSet();
+
+    public static <T extends ContentProvider> T providerWithRenamedContext(
+            Class<T> contentProvider, Context c, String filePrefix)
+            throws IllegalAccessException, InstantiationException {
+        return providerWithRenamedContext(contentProvider, c, filePrefix, false);
+    }
+
+    public static <T extends ContentProvider> T providerWithRenamedContext(
+            Class<T> contentProvider, Context c, String filePrefix,
+            boolean allowAccessToExistingFilesAndDbs)
+            throws IllegalAccessException, InstantiationException {
+        Class<T> mProviderClass = contentProvider;
+        T mProvider = mProviderClass.newInstance();
+        RenamingDelegatingContext mContext = new RenamingDelegatingContext(c, filePrefix);
+        if (allowAccessToExistingFilesAndDbs) {
+            mContext.makeExistingFilesAndDbsAccessible();
+        }
+        mProvider.attachInfoForTesting(mContext, null);
+        return mProvider;
+    }
+
+    /**
+     * Makes accessible all files and databases whose names match the filePrefix that was passed to
+     * the constructor. Normally only files and databases that were created through this context are
+     * accessible.
+     */
+    public void makeExistingFilesAndDbsAccessible() {
+        String[] databaseList = mFileContext.databaseList();
+        for (String diskName : databaseList) {
+            if (shouldDiskNameBeVisible(diskName)) {
+                mDatabaseNames.add(publicNameFromDiskName(diskName));
+            }
+        }
+        String[] fileList = mFileContext.fileList();
+        for (String diskName : fileList) {
+            if (shouldDiskNameBeVisible(diskName)) {
+                mFileNames.add(publicNameFromDiskName(diskName));
+            }
+        }
+    }
+
+    /**
+     * Returns if the given diskName starts with the given prefix or not.
+     * @param diskName name of the database/file.
+     */
+    boolean shouldDiskNameBeVisible(String diskName) {
+        return diskName.startsWith(mFilePrefix);
+    }
+
+    /**
+     * Returns the public name (everything following the prefix) of the given diskName.
+     * @param diskName name of the database/file.
+     */
+    String publicNameFromDiskName(String diskName) {
+        if (!shouldDiskNameBeVisible(diskName)) {
+            throw new IllegalArgumentException("disk file should not be visible: " + diskName);
+        }
+        return diskName.substring(mFilePrefix.length(), diskName.length());
+    }
+
+    /**
+     * @param context : the context that will be delegated.
+     * @param filePrefix : a prefix with which database and file names will be
+     * prefixed.
+     */
+    public RenamingDelegatingContext(Context context, String filePrefix) {
+        super(context);
+        mFileContext = context;
+        mFilePrefix = filePrefix;
+    }
+
+    /**
+     * @param context : the context that will be delegated.
+     * @param fileContext : the context that file and db methods will be delegated to
+     * @param filePrefix : a prefix with which database and file names will be
+     * prefixed.
+     */
+    public RenamingDelegatingContext(Context context, Context fileContext, String filePrefix) {
+        super(context);
+        mFileContext = fileContext;
+        mFilePrefix = filePrefix;
+    }
+
+    public String getDatabasePrefix() {
+        return mFilePrefix;
+    }
+
+    private String renamedFileName(String name) {
+        return mFilePrefix + name;
+    }
+
+    @Override
+    public SQLiteDatabase openOrCreateDatabase(String name,
+            int mode, SQLiteDatabase.CursorFactory factory) {
+        if (name.startsWith("/")) {
+            return mFileContext.openOrCreateDatabase(name, mode, factory);
+        }
+        final String internalName = renamedFileName(name);
+        if (!mDatabaseNames.contains(name)) {
+            mDatabaseNames.add(name);
+            mFileContext.deleteDatabase(internalName);
+        }
+        return mFileContext.openOrCreateDatabase(internalName, mode, factory);
+    }
+
+    @Override
+    public SQLiteDatabase openOrCreateDatabase(String name,
+            int mode, SQLiteDatabase.CursorFactory factory, DatabaseErrorHandler errorHandler) {
+        if (name.startsWith("/")) {
+            return mFileContext.openOrCreateDatabase(name, mode, factory, errorHandler);
+        }
+        final String internalName = renamedFileName(name);
+        if (!mDatabaseNames.contains(name)) {
+            mDatabaseNames.add(name);
+            mFileContext.deleteDatabase(internalName);
+        }
+        return mFileContext.openOrCreateDatabase(internalName, mode, factory, errorHandler);
+    }
+
+    @Override
+    public boolean deleteDatabase(String name) {
+        if (name.startsWith("/")) {
+            return mFileContext.deleteDatabase(name);
+        }
+        if (mDatabaseNames.contains(name)) {
+            mDatabaseNames.remove(name);
+            return mFileContext.deleteDatabase(renamedFileName(name));
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public File getDatabasePath(String name) {
+        if (name.startsWith("/")) {
+            return mFileContext.getDatabasePath(name);
+        }
+        return mFileContext.getDatabasePath(renamedFileName(name));
+    }
+
+    @Override
+    public String[] databaseList() {
+        return mDatabaseNames.toArray(new String[]{});
+    }
+
+    @Override
+    public FileInputStream openFileInput(String name)
+            throws FileNotFoundException {
+        final String internalName = renamedFileName(name);
+        if (mFileNames.contains(name)) {
+            return mFileContext.openFileInput(internalName);
+        } else {
+            throw new FileNotFoundException(internalName);
+        }
+    }
+
+    @Override
+    public FileOutputStream openFileOutput(String name, int mode)
+            throws FileNotFoundException {
+        mFileNames.add(name);
+        return mFileContext.openFileOutput(renamedFileName(name), mode);
+    }
+
+    @Override
+    public File getFileStreamPath(String name) {
+        return mFileContext.getFileStreamPath(renamedFileName(name));
+    }
+
+    @Override
+    public boolean deleteFile(String name) {
+        if (mFileNames.contains(name)) {
+            mFileNames.remove(name);
+            return mFileContext.deleteFile(renamedFileName(name));
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public String[] fileList() {
+        return mFileNames.toArray(new String[]{});
+    }
+
+    /**
+     * In order to support calls to getCacheDir(), we create a temp cache dir (inside the real
+     * one) and return it instead.  This code is basically getCacheDir(), except it uses the real
+     * cache dir as the parent directory and creates a test cache dir inside that.
+     */
+    @Override
+    public File getCacheDir() {
+        synchronized (mSync) {
+            if (mCacheDir == null) {
+                mCacheDir = new File(mFileContext.getCacheDir(), renamedFileName("cache"));
+            }
+            if (!mCacheDir.exists()) {
+                if(!mCacheDir.mkdirs()) {
+                    Log.w("RenamingDelegatingContext", "Unable to create cache directory");
+                    return null;
+                }
+                FileUtils.setPermissions(
+                        mCacheDir.getPath(),
+                        FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
+                        -1, -1);
+            }
+        }
+        return mCacheDir;
+    }
+}
diff --git a/tests/src/com/android/providers/contacts/SecondaryUserContactsProvider2.java b/tests/src/com/android/providers/contacts/SecondaryUserContactsProvider2.java
new file mode 100644
index 0000000..61ae1ca
--- /dev/null
+++ b/tests/src/com/android/providers/contacts/SecondaryUserContactsProvider2.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2014 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.providers.contacts;
+
+import android.content.Context;
+
+/**
+ * A subclass of {@link SynchronousContactsProvider2} that uses a different DB for secondary users.
+ */
+public class SecondaryUserContactsProvider2 extends SynchronousContactsProvider2 {
+    private final String mDbSuffix;
+    private ContactsDatabaseHelper mDbHelper;
+
+    public SecondaryUserContactsProvider2(int userId) {
+        mDbSuffix = "-u" + userId;
+    }
+
+    @Override
+    public ContactsDatabaseHelper newDatabaseHelper(final Context context) {
+        if (mDbHelper == null) {
+            mDbHelper = ContactsDatabaseHelper.getNewInstanceForTest(context,
+                    TestUtils.getContactsDatabaseFilename(context, mDbSuffix));
+        }
+        return mDbHelper;
+    }
+}
diff --git a/tests/src/com/android/providers/contacts/StandaloneContactsProvider2.java b/tests/src/com/android/providers/contacts/StandaloneContactsProvider2.java
deleted file mode 100644
index 8dd09bf..0000000
--- a/tests/src/com/android/providers/contacts/StandaloneContactsProvider2.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2014 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.providers.contacts;
-
-import android.content.Context;
-
-/**
- * A subclass of {@link SynchronousContactsProvider2} that doesn't reuse the database helper.
- */
-public class StandaloneContactsProvider2 extends SynchronousContactsProvider2 {
-    private static ContactsDatabaseHelper mDbHelper;
-
-    public StandaloneContactsProvider2() {
-        // No need to wipe data for this instance since it doesn't reuse the db helper.
-        setDataWipeEnabled(false);
-    }
-
-    @Override
-    public ContactsDatabaseHelper getDatabaseHelper(final Context context) {
-        if (mDbHelper == null) {
-            mDbHelper = ContactsDatabaseHelper.getNewInstanceForTest(context);
-        }
-        return mDbHelper;
-    }
-
-    @Override
-    public void setDataWipeEnabled(boolean flag) {
-        // No need to wipe data for this instance since it doesn't reuse the db helper.
-        super.setDataWipeEnabled(false);
-    }
-}
diff --git a/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java b/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java
index 19878f8..c96c5b2 100644
--- a/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java
+++ b/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java
@@ -34,7 +34,6 @@
 
     private static Boolean sDataWiped = false;
     private static ContactsDatabaseHelper sDbHelper;
-    private boolean mDataWipeEnabled = true;
     private Account mAccount;
     private boolean mNetworkNotified;
     private boolean mMetadataNetworkNotified;
@@ -42,9 +41,10 @@
     private boolean mIsVoiceCapable = true;
 
     @Override
-    public ContactsDatabaseHelper getDatabaseHelper(final Context context) {
+    public ContactsDatabaseHelper newDatabaseHelper(final Context context) {
         if (sDbHelper == null) {
-            sDbHelper = ContactsDatabaseHelper.getNewInstanceForTest(context);
+            sDbHelper = ContactsDatabaseHelper.getNewInstanceForTest(context,
+                    TestUtils.getContactsDatabaseFilename(getContext()));
         }
         return sDbHelper;
     }
@@ -54,10 +54,6 @@
         return new SynchronousProfileProvider(this);
     }
 
-    public void setDataWipeEnabled(boolean flag) {
-        mDataWipeEnabled = flag;
-    }
-
     @Override
     public void onBegin() {
         super.onBegin();
@@ -100,12 +96,10 @@
     @Override
     public boolean onCreate() {
         boolean created = super.onCreate();
-        if (mDataWipeEnabled) {
-            synchronized (sDataWiped) {
-                if (!sDataWiped) {
-                    sDataWiped = true;
-                    wipeData();
-                }
+        synchronized (sDataWiped) {
+            if (!sDataWiped) {
+                sDataWiped = true;
+                wipeData();
             }
         }
         return created;
@@ -192,7 +186,7 @@
     }
 
     public void prepareForFullAggregation(int maxContact) {
-        SQLiteDatabase db = getDatabaseHelper(getContext()).getWritableDatabase();
+        SQLiteDatabase db = getDatabaseHelper().getWritableDatabase();
         db.execSQL("UPDATE raw_contacts SET aggregation_mode=0,aggregation_needed=1;");
         long rowId =
             db.compileStatement("SELECT _id FROM raw_contacts LIMIT 1 OFFSET " + maxContact)
@@ -201,12 +195,12 @@
     }
 
     public long getRawContactCount() {
-        SQLiteDatabase db = getDatabaseHelper(getContext()).getReadableDatabase();
+        SQLiteDatabase db = getDatabaseHelper().getReadableDatabase();
         return db.compileStatement("SELECT COUNT(*) FROM raw_contacts").simpleQueryForLong();
     }
 
     public long getContactCount() {
-        SQLiteDatabase db = getDatabaseHelper(getContext()).getReadableDatabase();
+        SQLiteDatabase db = getDatabaseHelper().getReadableDatabase();
         return db.compileStatement("SELECT COUNT(*) FROM contacts").simpleQueryForLong();
     }
 
@@ -214,7 +208,7 @@
     public void wipeData() {
         Log.i(TAG, "wipeData");
         super.wipeData();
-        SQLiteDatabase db = getDatabaseHelper(getContext()).getWritableDatabase();
+        SQLiteDatabase db = getDatabaseHelper().getWritableDatabase();
         db.execSQL("replace into SQLITE_SEQUENCE (name,seq) values('raw_contacts', 42)");
         db.execSQL("replace into SQLITE_SEQUENCE (name,seq) values('contacts', 2009)");
         db.execSQL("replace into SQLITE_SEQUENCE (name,seq) values('data', 777)");
diff --git a/tests/src/com/android/providers/contacts/SynchronousProfileProvider.java b/tests/src/com/android/providers/contacts/SynchronousProfileProvider.java
index 308e67a..79356a5 100644
--- a/tests/src/com/android/providers/contacts/SynchronousProfileProvider.java
+++ b/tests/src/com/android/providers/contacts/SynchronousProfileProvider.java
@@ -32,9 +32,10 @@
     }
 
     @Override
-    protected ProfileDatabaseHelper getDatabaseHelper(final Context context) {
+    protected ProfileDatabaseHelper newDatabaseHelper(final Context context) {
         if (sDbHelper == null) {
-            sDbHelper = ProfileDatabaseHelper.getNewInstanceForTest(context);
+            sDbHelper = ProfileDatabaseHelper.getNewInstanceForTest(context,
+                    TestUtils.getProfileDatabaseFilename(getContext()));
         }
         return sDbHelper;
     }
diff --git a/tests/src/com/android/providers/contacts/TestUtils.java b/tests/src/com/android/providers/contacts/TestUtils.java
index b6d6a27..5e88548 100644
--- a/tests/src/com/android/providers/contacts/TestUtils.java
+++ b/tests/src/com/android/providers/contacts/TestUtils.java
@@ -19,6 +19,8 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
+import android.os.FileUtils;
+import android.support.annotation.Nullable;
 import android.util.Log;
 
 import junit.framework.Assert;
@@ -28,9 +30,89 @@
 import java.io.IOException;
 
 public class TestUtils {
+    private static final String TAG = "ContactsTestUtils";
+
     private TestUtils() {
     }
 
+    /**
+     * We normally use in-memory DBs in unit tests, because that's faster, but it's impossible to
+     * look at intermediate databases when something is failing.  When this flag is set to true,
+     * we'll switch to file-based DBs, so we can call {@link #createDatabaseSnapshot}
+     * , pull the snapshot DBs and take a look at them.
+     */
+    public static final boolean ENABLE_DATABASE_SNAPSHOT = false; // DO NOT SUBMIT WITH TRUE.
+
+    private static final Object sDatabasePathLock = new Object();
+    private static File sDatabasePath = null;
+
+    private static String getDatabaseFile(Context context, @Nullable String name) {
+        if (!ENABLE_DATABASE_SNAPSHOT) {
+            return null; // Use the in-memory DB.
+        }
+        synchronized (sDatabasePathLock) {
+            if (sDatabasePath == null) {
+                final File path = new File(context.getCacheDir(), "test-db");
+                if (path.exists()) {
+                    Assert.assertTrue("Unable to delete directory: " + path,
+                            FileUtils.deleteContents(path));
+                } else {
+                    Assert.assertTrue("Unable to create directory: " + path, path.mkdirs());
+                }
+
+                sDatabasePath = path;
+            }
+            if (name == null) {
+                return sDatabasePath.getAbsolutePath();
+            } else {
+                return new File(sDatabasePath, name).getAbsolutePath();
+            }
+        }
+    }
+
+    public static String getContactsDatabaseFilename(Context context) {
+        return getContactsDatabaseFilename(context, "");
+    }
+
+    public static String getContactsDatabaseFilename(Context context, String suffix) {
+        return getDatabaseFile(context, "contacts2" + suffix + ".db");
+    }
+
+    public static String getProfileDatabaseFilename(Context context) {
+        return getProfileDatabaseFilename(context, "");
+    }
+
+    public static String getProfileDatabaseFilename(Context context, String suffix) {
+        return getDatabaseFile(context, "profile.db" + suffix + ".db");
+    }
+
+    public static void createDatabaseSnapshot(Context context, String name) {
+        Assert.assertTrue(
+                "ENABLE_DATABASE_SNAPSHOT must be set to true to create database snapshot",
+                ENABLE_DATABASE_SNAPSHOT);
+
+        final File fromDir = new File(getDatabaseFile(context, null));
+        final File toDir = new File(context.getCacheDir(), "snapshot-" + name);
+        if (toDir.exists()) {
+            Assert.assertTrue("Unable to delete directory: " + toDir,
+                    FileUtils.deleteContents(toDir));
+        } else {
+            Assert.assertTrue("Unable to create directory: " + toDir, toDir.mkdirs());
+        }
+
+        Log.w(TAG, "Copying database files into '" + toDir + "'...");
+
+        for (File file : fromDir.listFiles()) {
+            try {
+                final File to = new File(toDir, file.getName());
+                FileUtils.copyFileOrThrow(file, to);
+                Log.i(TAG, "Created: " + to);
+            } catch (IOException e) {
+                Assert.fail("Failed to copy file: " + e.toString());
+            }
+        }
+    }
+
     /** Convenient method to create a ContentValues */
     public static ContentValues cv(Object... namesAndValues) {
         Assert.assertTrue((namesAndValues.length % 2) == 0);
diff --git a/tests/src/com/android/providers/contacts/sqlite/DatabaseAnalyzerTest.java b/tests/src/com/android/providers/contacts/sqlite/DatabaseAnalyzerTest.java
index 0ef020b..568e1e8 100644
--- a/tests/src/com/android/providers/contacts/sqlite/DatabaseAnalyzerTest.java
+++ b/tests/src/com/android/providers/contacts/sqlite/DatabaseAnalyzerTest.java
@@ -19,13 +19,15 @@
 import android.test.AndroidTestCase;
 
 import com.android.providers.contacts.ContactsDatabaseHelper;
+import com.android.providers.contacts.TestUtils;
 
 import java.util.List;
 
 public class DatabaseAnalyzerTest extends AndroidTestCase {
     public void testFindTableViewsAllowingColumns() {
         final ContactsDatabaseHelper dbh =
-                ContactsDatabaseHelper.getNewInstanceForTest(getContext());
+                ContactsDatabaseHelper.getNewInstanceForTest(getContext(),
+                        TestUtils.getContactsDatabaseFilename(getContext()));
         try {
             final List<String> list =  DatabaseAnalyzer.findTableViewsAllowingColumns(
                     dbh.getReadableDatabase());