Merge commit 'e0c5d80d' into mit

* commit 'e0c5d80d':
  Use telephony-common

Change-Id: I56b4a75ac390e7710d3c3a9275a68c5dd6d20c2c
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index f958b12..0678347 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -17,13 +17,13 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="sharedUserLabel" msgid="8024311725474286801">"Apl Inti Android"</string>
-    <string name="app_label" msgid="3389954322874982620">"Penyimpanan Kenalan"</string>
-    <string name="provider_label" msgid="6012150850819899907">"Kenalan"</string>
-    <string name="upgrade_msg" msgid="8640807392794309950">"Meningkatkan versi basis data kenalan."</string>
-    <string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Peningkatan versi kenalan memerlukan lebih banyak memori."</string>
-    <string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Meningkatkan versi penyimpanan untuk kenalan"</string>
+    <string name="app_label" msgid="3389954322874982620">"Penyimpanan Kontak"</string>
+    <string name="provider_label" msgid="6012150850819899907">"Kontak"</string>
+    <string name="upgrade_msg" msgid="8640807392794309950">"Meningkatkan versi basis kontak."</string>
+    <string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Peningkatan versi kontak memerlukan lebih banyak memori."</string>
+    <string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Meningkatkan versi penyimpanan untuk kontak"</string>
     <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Sentuh untuk menyelesaikan peningkatan versi."</string>
-    <string name="default_directory" msgid="93961630309570294">"Kenalan"</string>
+    <string name="default_directory" msgid="93961630309570294">"Kontak"</string>
     <string name="local_invisible_directory" msgid="705244318477396120">"Lainnya"</string>
     <string name="read_write_all_voicemail_label" msgid="4557216100818257560">"Akses semua pesan suara"</string>
     <string name="read_write_all_voicemail_description" msgid="8029809937805761356">"Mengizinkan apl menyimpan dan mengambil semua pesan suara yang dapat diakses oleh perangkat ini."</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 402b8c1..baaf5ee 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -22,7 +22,7 @@
     <string name="upgrade_msg" msgid="8640807392794309950">"正在升级联系人数据库。"</string>
     <string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"联系人升级需要更多的存储空间。"</string>
     <string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"正在升级存储器以容纳更多联系人"</string>
-    <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"请触摸以完成升级。"</string>
+    <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"触摸可完成升级。"</string>
     <string name="default_directory" msgid="93961630309570294">"联系人"</string>
     <string name="local_invisible_directory" msgid="705244318477396120">"其他"</string>
     <string name="read_write_all_voicemail_label" msgid="4557216100818257560">"访问所有语音邮件"</string>
diff --git a/src/com/android/providers/contacts/AbstractContactsProvider.java b/src/com/android/providers/contacts/AbstractContactsProvider.java
index a33320f..226652e 100644
--- a/src/com/android/providers/contacts/AbstractContactsProvider.java
+++ b/src/com/android/providers/contacts/AbstractContactsProvider.java
@@ -38,9 +38,12 @@
 public abstract class AbstractContactsProvider extends ContentProvider
         implements SQLiteTransactionListener {
 
-    protected static final String TAG = "ContactsProvider";
+    public static final String TAG = "ContactsProvider";
 
-    protected static final boolean VERBOSE_LOGGING = Log.isLoggable(TAG, Log.VERBOSE);
+    public static final boolean VERBOSE_LOGGING = Log.isLoggable(TAG, Log.VERBOSE);
+
+    /** Set true to enable detailed transaction logging. */
+    public static final boolean ENABLE_TRANSACTION_LOG = false; // Don't submit with true.
 
     /**
      * Duration in ms to sleep after successfully yielding the lock during a batch operation.
@@ -72,14 +75,32 @@
      * created by this provider will automatically retrieve a writable database from this helper
      * and initiate a transaction on that database.  This should be used to ensure that operations
      * across multiple databases are all blocked on a single DB lock (to prevent deadlock cases).
+     *
+     * Hint: It's always {@link ContactsDatabaseHelper}.
+     *
+     * TODO Change the structure to make it obvious that it's actually always set, and is the
+     * {@link ContactsDatabaseHelper}.
      */
     private SQLiteOpenHelper mSerializeOnDbHelper;
 
     /**
      * The tag corresponding to the database used for serializing transactions.
+     *
+     * Hint: It's always the contacts db helper tag.
+     *
+     * See also the TODO on {@link #mSerializeOnDbHelper}.
      */
     private String mSerializeDbTag;
 
+    /**
+     * The transaction listener used with {@link #mSerializeOnDbHelper}.
+     *
+     * Hint: It's always {@link ContactsProvider2}.
+     *
+     * See also the TODO on {@link #mSerializeOnDbHelper}.
+     */
+    private SQLiteTransactionListener mSerializedDbTransactionListener;
+
     @Override
     public boolean onCreate() {
         Context context = getContext();
@@ -94,12 +115,14 @@
 
     /**
      * Specifies a database helper (and corresponding tag) to serialize all transactions on.
-     * @param serializeOnDbHelper The database helper to use for serializing transactions.
-     * @param tag The tag for this database.
+     *
+     * See also the TODO on {@link #mSerializeOnDbHelper}.
      */
-    public void setDbHelperToSerializeOn(SQLiteOpenHelper serializeOnDbHelper, String tag) {
+    public void setDbHelperToSerializeOn(SQLiteOpenHelper serializeOnDbHelper, String tag,
+            SQLiteTransactionListener listener) {
         mSerializeOnDbHelper = serializeOnDbHelper;
         mSerializeDbTag = tag;
+        mSerializedDbTransactionListener = listener;
     }
 
     public ContactsTransaction getCurrentTransaction() {
@@ -227,12 +250,16 @@
      * @param callerIsBatch Whether the caller is operating in batch mode.
      */
     private ContactsTransaction startTransaction(boolean callerIsBatch) {
+        if (ENABLE_TRANSACTION_LOG) {
+            Log.i(TAG, "startTransaction " + getClass().getSimpleName() +
+                    "  callerIsBatch=" + callerIsBatch, new RuntimeException("startTransaction"));
+        }
         ContactsTransaction transaction = mTransactionHolder.get();
         if (transaction == null) {
             transaction = new ContactsTransaction(callerIsBatch);
             if (mSerializeOnDbHelper != null) {
                 transaction.startTransactionForDb(mSerializeOnDbHelper.getWritableDatabase(),
-                        mSerializeDbTag, this);
+                        mSerializeDbTag, mSerializedDbTransactionListener);
             }
             mTransactionHolder.set(transaction);
         }
@@ -245,6 +272,10 @@
      * @param callerIsBatch Whether the caller is operating in batch mode.
      */
     private void endTransaction(boolean callerIsBatch) {
+        if (ENABLE_TRANSACTION_LOG) {
+            Log.i(TAG, "endTransaction " + getClass().getSimpleName() +
+                    "  callerIsBatch=" + callerIsBatch, new RuntimeException("endTransaction"));
+        }
         ContactsTransaction transaction = mTransactionHolder.get();
         if (transaction != null && (!transaction.isBatch() || callerIsBatch)) {
             try {
diff --git a/src/com/android/providers/contacts/AccountWithDataSet.java b/src/com/android/providers/contacts/AccountWithDataSet.java
index 3fea8a6..b7484cd 100644
--- a/src/com/android/providers/contacts/AccountWithDataSet.java
+++ b/src/com/android/providers/contacts/AccountWithDataSet.java
@@ -16,11 +16,11 @@
 
 package com.android.providers.contacts;
 
-import com.android.internal.util.Objects;
-
 import android.accounts.Account;
 import android.text.TextUtils;
 
+import com.android.internal.util.Objects;
+
 /**
  * Account information that includes the data set, if any.
  */
diff --git a/src/com/android/providers/contacts/CallLogProvider.java b/src/com/android/providers/contacts/CallLogProvider.java
index 89ae591..21a16f1 100644
--- a/src/com/android/providers/contacts/CallLogProvider.java
+++ b/src/com/android/providers/contacts/CallLogProvider.java
@@ -20,10 +20,6 @@
 import static com.android.providers.contacts.util.DbQueryUtils.getEqualityClause;
 import static com.android.providers.contacts.util.DbQueryUtils.getInequalityClause;
 
-import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
-import com.android.providers.contacts.util.SelectionBuilder;
-import com.google.common.annotations.VisibleForTesting;
-
 import android.content.ContentProvider;
 import android.content.ContentUris;
 import android.content.ContentValues;
@@ -38,6 +34,10 @@
 import android.provider.CallLog.Calls;
 import android.util.Log;
 
+import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
+import com.android.providers.contacts.util.SelectionBuilder;
+import com.google.common.annotations.VisibleForTesting;
+
 import java.util.HashMap;
 
 /**
diff --git a/src/com/android/providers/contacts/ContactDirectoryManager.java b/src/com/android/providers/contacts/ContactDirectoryManager.java
index 7116ed6..f243e79 100644
--- a/src/com/android/providers/contacts/ContactDirectoryManager.java
+++ b/src/com/android/providers/contacts/ContactDirectoryManager.java
@@ -16,13 +16,6 @@
 
 package com.android.providers.contacts;
 
-import com.android.providers.contacts.ContactsDatabaseHelper.DbProperties;
-import com.android.providers.contacts.ContactsDatabaseHelper.DirectoryColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
-import com.google.android.collect.Lists;
-import com.google.android.collect.Sets;
-import com.google.common.annotations.VisibleForTesting;
-
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.pm.PackageInfo;
@@ -41,6 +34,13 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.providers.contacts.ContactsDatabaseHelper.DbProperties;
+import com.android.providers.contacts.ContactsDatabaseHelper.DirectoryColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
+import com.google.android.collect.Lists;
+import com.google.android.collect.Sets;
+import com.google.common.annotations.VisibleForTesting;
+
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
diff --git a/src/com/android/providers/contacts/ContactLocaleUtils.java b/src/com/android/providers/contacts/ContactLocaleUtils.java
index 2eb2ad6..0e7b292 100644
--- a/src/com/android/providers/contacts/ContactLocaleUtils.java
+++ b/src/com/android/providers/contacts/ContactLocaleUtils.java
@@ -16,11 +16,11 @@
 
 package com.android.providers.contacts;
 
-import com.android.providers.contacts.HanziToPinyin.Token;
-
 import android.provider.ContactsContract.FullNameStyle;
 import android.util.SparseArray;
 
+import com.android.providers.contacts.HanziToPinyin.Token;
+
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.Iterator;
diff --git a/src/com/android/providers/contacts/ContactsDatabaseHelper.java b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
index 67429ca..62f49ae 100644
--- a/src/com/android/providers/contacts/ContactsDatabaseHelper.java
+++ b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
@@ -16,11 +16,6 @@
 
 package com.android.providers.contacts;
 
-import com.android.common.content.SyncStateContentProviderHelper;
-import com.android.providers.contacts.aggregation.util.CommonNicknameCache;
-import com.android.providers.contacts.util.NeededForTesting;
-import com.google.android.collect.Sets;
-
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
@@ -80,6 +75,11 @@
 import android.text.util.Rfc822Tokenizer;
 import android.util.Log;
 
+import com.android.common.content.SyncStateContentProviderHelper;
+import com.android.providers.contacts.aggregation.util.CommonNicknameCache;
+import com.android.providers.contacts.util.NeededForTesting;
+import com.google.android.collect.Sets;
+
 import java.util.HashMap;
 import java.util.Locale;
 import java.util.Set;
@@ -779,6 +779,10 @@
                         com.android.internal.R.bool.config_use_strict_phone_number_comparation);
     }
 
+    public SQLiteDatabase getDatabase(boolean writable) {
+        return writable ? getWritableDatabase() : getReadableDatabase();
+    }
+
     /**
      * Clear all the cached database information and re-initialize it.
      *
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index c321489..484aa79 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -16,51 +16,6 @@
 
 package com.android.providers.contacts;
 
-import com.android.common.content.ProjectionMap;
-import com.android.common.content.SyncStateContentProviderHelper;
-import com.android.providers.contacts.ContactLookupKey.LookupKeySegment;
-import com.android.providers.contacts.ContactsDatabaseHelper.AccountsColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.AggregatedPresenceColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.AggregationExceptionColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.Clauses;
-import com.android.providers.contacts.ContactsDatabaseHelper.ContactsColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.ContactsStatusUpdatesColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.DataColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.DataUsageStatColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.DbProperties;
-import com.android.providers.contacts.ContactsDatabaseHelper.GroupsColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.Joins;
-import com.android.providers.contacts.ContactsDatabaseHelper.NameLookupColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.NameLookupType;
-import com.android.providers.contacts.ContactsDatabaseHelper.PhoneLookupColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.PhotoFilesColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.PresenceColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.Projections;
-import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.SearchIndexColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.SettingsColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.StatusUpdatesColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.StreamItemPhotosColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.StreamItemsColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
-import com.android.providers.contacts.ContactsDatabaseHelper.ViewGroupsColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.Views;
-import com.android.providers.contacts.SearchIndexManager.FtsQueryBuilder;
-import com.android.providers.contacts.aggregation.ContactAggregator;
-import com.android.providers.contacts.aggregation.ContactAggregator.AggregationSuggestionParameter;
-import com.android.providers.contacts.aggregation.util.CommonNicknameCache;
-import com.android.providers.contacts.aggregation.ProfileAggregator;
-import com.android.providers.contacts.util.Clock;
-import com.android.providers.contacts.util.DbQueryUtils;
-import com.android.providers.contacts.util.NeededForTesting;
-import com.android.vcard.VCardComposer;
-import com.android.vcard.VCardConfig;
-import com.google.android.collect.Lists;
-import com.google.android.collect.Maps;
-import com.google.android.collect.Sets;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Preconditions;
-
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.accounts.OnAccountsUpdateListener;
@@ -151,6 +106,51 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.common.content.ProjectionMap;
+import com.android.common.content.SyncStateContentProviderHelper;
+import com.android.providers.contacts.ContactLookupKey.LookupKeySegment;
+import com.android.providers.contacts.ContactsDatabaseHelper.AccountsColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.AggregatedPresenceColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.AggregationExceptionColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.Clauses;
+import com.android.providers.contacts.ContactsDatabaseHelper.ContactsColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.ContactsStatusUpdatesColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.DataColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.DataUsageStatColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.DbProperties;
+import com.android.providers.contacts.ContactsDatabaseHelper.GroupsColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.Joins;
+import com.android.providers.contacts.ContactsDatabaseHelper.NameLookupColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.NameLookupType;
+import com.android.providers.contacts.ContactsDatabaseHelper.PhoneLookupColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.PhotoFilesColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.PresenceColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.Projections;
+import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.SearchIndexColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.SettingsColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.StatusUpdatesColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.StreamItemPhotosColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.StreamItemsColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
+import com.android.providers.contacts.ContactsDatabaseHelper.ViewGroupsColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.Views;
+import com.android.providers.contacts.SearchIndexManager.FtsQueryBuilder;
+import com.android.providers.contacts.aggregation.ContactAggregator;
+import com.android.providers.contacts.aggregation.ContactAggregator.AggregationSuggestionParameter;
+import com.android.providers.contacts.aggregation.ProfileAggregator;
+import com.android.providers.contacts.aggregation.util.CommonNicknameCache;
+import com.android.providers.contacts.util.Clock;
+import com.android.providers.contacts.util.DbQueryUtils;
+import com.android.providers.contacts.util.NeededForTesting;
+import com.android.vcard.VCardComposer;
+import com.android.vcard.VCardConfig;
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+import com.google.android.collect.Sets;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+
 import java.io.BufferedWriter;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
@@ -1264,13 +1264,6 @@
     /* package */ static final String PROFILE_DB_TAG = "profile";
 
     /**
-     * The active (thread-local) database.  This will be switched between a contacts-specific
-     * database and a profile-specific database, depending on what the current operation is
-     * targeted to.
-     */
-    private final ThreadLocal<SQLiteDatabase> mActiveDb = new ThreadLocal<SQLiteDatabase>();
-
-    /**
      * The thread-local holder of the active transaction.  Shared between this and the profile
      * provider, to keep transactions on both databases synchronized.
      */
@@ -1393,7 +1386,7 @@
         mDbHelper.set(mContactsHelper);
 
         // Set up the DB helper for keeping transactions serialized.
-        setDbHelperToSerializeOn(mContactsHelper, CONTACTS_DB_TAG);
+        setDbHelperToSerializeOn(mContactsHelper, CONTACTS_DB_TAG, this);
 
         mContactDirectoryManager = new ContactDirectoryManager(this);
         mGlobalSearchSupport = new GlobalSearchSupport(this);
@@ -1413,8 +1406,8 @@
         };
 
         // Set up the sub-provider for handling profiles.
-        mProfileProvider = getProfileProvider();
-        mProfileProvider.setDbHelperToSerializeOn(mContactsHelper, CONTACTS_DB_TAG);
+        mProfileProvider = newProfileProvider();
+        mProfileProvider.setDbHelperToSerializeOn(mContactsHelper, CONTACTS_DB_TAG, this);
         ProviderInfo profileInfo = new ProviderInfo();
         profileInfo.readPermission = "android.permission.READ_PROFILE";
         profileInfo.writePermission = "android.permission.WRITE_PROFILE";
@@ -1521,6 +1514,8 @@
     }
 
     protected void performBackgroundTask(int task, Object arg) {
+        // Make sure we operate on the contacts db by default.
+        switchToContactMode();
         switch (task) {
             case BACKGROUND_TASK_INITIALIZE: {
                 initForDefaultLocale();
@@ -1551,6 +1546,8 @@
                 switchToProfileMode();
                 accountsChanged |= updateAccountsInBackground(accounts);
 
+                switchToContactMode();
+
                 updateContactsAccountCount(accounts);
                 updateDirectoriesInBackground(accountsChanged);
                 break;
@@ -1601,6 +1598,8 @@
                     cleanupPhotoStore();
                     switchToProfileMode();
                     cleanupPhotoStore();
+
+                    switchToContactMode(); // Switch to the default, just in case.
                     break;
                 }
             }
@@ -1705,8 +1704,7 @@
 
     @VisibleForTesting
     protected void cleanupPhotoStore() {
-        SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
-        mActiveDb.set(db);
+        final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
 
         // Assemble the set of photo store file IDs that are in use, and send those to the photo
         // store.  Any photos that aren't in that set will be deleted, and any photos that no
@@ -1759,7 +1757,9 @@
         // using internal APIs or direct DB access to avoid permission errors.
         if (!missingPhotoIds.isEmpty()) {
             try {
-                db.beginTransactionWithListener(this);
+                // Need to set the db listener because we need to run onCommit afterwards.
+                // Make sure to use the proper listener depending on the current mode.
+                db.beginTransactionWithListener(inProfileMode() ? mProfileProvider : this);
                 for (long missingPhotoId : missingPhotoIds) {
                     if (photoFileIdToDataId.containsKey(missingPhotoId)) {
                         long dataId = photoFileIdToDataId.get(missingPhotoId);
@@ -1796,7 +1796,7 @@
         return mTransactionHolder;
     }
 
-    public ProfileProvider getProfileProvider() {
+    public ProfileProvider newProfileProvider() {
         return new ProfileProvider(this);
     }
 
@@ -1835,7 +1835,8 @@
         return Locale.getDefault();
     }
 
-    private boolean inProfileMode() {
+    @VisibleForTesting
+    final boolean inProfileMode() {
         Boolean profileMode = mInProfileMode.get();
         return profileMode != null && profileMode;
     }
@@ -1921,7 +1922,10 @@
      * Switches the provider's thread-local context variables to prepare for performing
      * a profile operation.
      */
-    protected void switchToProfileMode() {
+    private void switchToProfileMode() {
+        if (ENABLE_TRANSACTION_LOG) {
+            Log.i(TAG, "switchToProfileMode", new RuntimeException("switchToProfileMode"));
+        }
         mDbHelper.set(mProfileHelper);
         mTransactionContext.set(mProfileTransactionContext);
         mAggregator.set(mProfileAggregator);
@@ -1933,15 +1937,15 @@
      * Switches the provider's thread-local context variables to prepare for performing
      * a contacts operation.
      */
-    protected void switchToContactMode() {
+    private void switchToContactMode() {
+        if (ENABLE_TRANSACTION_LOG) {
+            Log.i(TAG, "switchToContactMode", new RuntimeException("switchToContactMode"));
+        }
         mDbHelper.set(mContactsHelper);
         mTransactionContext.set(mContactTransactionContext);
         mAggregator.set(mContactAggregator);
         mPhotoStore.set(mContactsPhotoStore);
         mInProfileMode.set(false);
-
-        // Clear out the active database; modification operations will set this to the contacts DB.
-        mActiveDb.set(null);
     }
 
     @Override
@@ -2000,17 +2004,10 @@
         }
     }
 
-    /**
-     * Replaces the current (thread-local) database to use for the operation with the given one.
-     * @param db The database to use.
-     */
-    /* package */ void substituteDb(SQLiteDatabase db) {
-        mActiveDb.set(db);
-    }
-
     @Override
     public Bundle call(String method, String arg, Bundle extras) {
         waitForAccess(mReadAccessLatch);
+        switchToContactMode();
         if (method.equals(Authorization.AUTHORIZATION_METHOD)) {
             Uri uri = (Uri) extras.getParcelable(Authorization.KEY_URI_TO_AUTHORIZE);
 
@@ -2110,13 +2107,20 @@
 
     @Override
     public void onBegin() {
-        if (VERBOSE_LOGGING) {
-            Log.v(TAG, "onBeginTransaction: " + (inProfileMode() ? "profile" : "contacts"));
+        onBeginTransactionInternal(false);
+    }
+
+    protected void onBeginTransactionInternal(boolean forProfile) {
+        if (ENABLE_TRANSACTION_LOG) {
+            Log.i(TAG, "onBeginTransaction: " + (forProfile ? "profile" : "contacts"),
+                    new RuntimeException("onBeginTransactionInternal"));
         }
-        if (inProfileMode()) {
+        if (forProfile) {
+            switchToProfileMode();
             mProfileAggregator.clearPendingAggregations();
             mProfileTransactionContext.clearExceptSearchIndexUpdates();
         } else {
+            switchToContactMode();
             mContactAggregator.clearPendingAggregations();
             mContactTransactionContext.clearExceptSearchIndexUpdates();
         }
@@ -2124,11 +2128,23 @@
 
     @Override
     public void onCommit() {
-        if (VERBOSE_LOGGING) {
-            Log.v(TAG, "beforeTransactionCommit: " + (inProfileMode() ? "profile" : "contacts"));
+        onCommitTransactionInternal(false);
+    }
+
+    protected void onCommitTransactionInternal(boolean forProfile) {
+        if (ENABLE_TRANSACTION_LOG) {
+            Log.i(TAG, "onCommitTransactionInternal: " + (forProfile ? "profile" : "contacts"),
+                    new RuntimeException("onCommitTransactionInternal"));
         }
+        if (forProfile) {
+            switchToProfileMode();
+        } else {
+            switchToContactMode();
+        }
+
         flushTransactionalChanges();
-        mAggregator.get().aggregateInTransaction(mTransactionContext.get(), mActiveDb.get());
+        final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
+        mAggregator.get().aggregateInTransaction(mTransactionContext.get(), db);
         if (mVisibleTouched) {
             mVisibleTouched = false;
             mDbHelper.get().updateAllVisible();
@@ -2147,13 +2163,21 @@
 
     @Override
     public void onRollback() {
-        if (VERBOSE_LOGGING) {
-            Log.v(TAG, "beforeTransactionRollback: " + (inProfileMode() ? "profile" : "contacts"));
+        onRollbackTransactionInternal(false);
+    }
+
+    protected void onRollbackTransactionInternal(boolean forProfile) {
+        if (ENABLE_TRANSACTION_LOG) {
+            Log.i(TAG, "onRollbackTransactionInternal: " + (forProfile ? "profile" : "contacts"),
+                    new RuntimeException("onRollbackTransactionInternal"));
         }
-        // mDbHelper may not be pointing at the "right" db helper due to a bug,
-        // so we invalidate both for now.
-        mContactsHelper.invalidateAllCache();
-        mProfileHelper.invalidateAllCache();
+        if (forProfile) {
+            switchToProfileMode();
+        } else {
+            switchToContactMode();
+        }
+
+        mDbHelper.get().invalidateAllCache();
     }
 
     private void updateSearchIndexInTransaction() {
@@ -2167,12 +2191,13 @@
 
     private void flushTransactionalChanges() {
         if (VERBOSE_LOGGING) {
-            Log.v(TAG, "flushTransactionChanges");
+            Log.v(TAG, "flushTransactionalChanges: " + (inProfileMode() ? "profile" : "contacts"));
         }
 
+        final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
         for (long rawContactId : mTransactionContext.get().getInsertedRawContactIds()) {
-            mDbHelper.get().updateRawContactDisplayName(mActiveDb.get(), rawContactId);
-            mAggregator.get().onRawContactInsert(mTransactionContext.get(), mActiveDb.get(),
+            mDbHelper.get().updateRawContactDisplayName(db, rawContactId);
+            mAggregator.get().onRawContactInsert(mTransactionContext.get(), db,
                     rawContactId);
         }
 
@@ -2182,7 +2207,7 @@
             mSb.append(UPDATE_RAW_CONTACT_SET_DIRTY_SQL);
             appendIds(mSb, dirtyRawContacts);
             mSb.append(")");
-            mActiveDb.get().execSQL(mSb.toString());
+            db.execSQL(mSb.toString());
         }
 
         Set<Long> updatedRawContacts = mTransactionContext.get().getUpdatedRawContactIds();
@@ -2191,13 +2216,13 @@
             mSb.append(UPDATE_RAW_CONTACT_SET_VERSION_SQL);
             appendIds(mSb, updatedRawContacts);
             mSb.append(")");
-            mActiveDb.get().execSQL(mSb.toString());
+            db.execSQL(mSb.toString());
         }
 
         // Update sync states.
         for (Map.Entry<Long, Object> entry : mTransactionContext.get().getUpdatedSyncStates()) {
             long id = entry.getKey();
-            if (mDbHelper.get().getSyncState().update(mActiveDb.get(), id, entry.getValue()) <= 0) {
+            if (mDbHelper.get().getSyncState().update(db, id, entry.getValue()) <= 0) {
                 throw new IllegalStateException(
                         "unable to update sync state, does it still exist?");
             }
@@ -2265,10 +2290,7 @@
             Log.v(TAG, "insertInTransaction: uri=" + uri + "  values=[" + values + "]");
         }
 
-        // Default active DB to the contacts DB if none has been set.
-        if (mActiveDb.get() == null) {
-            mActiveDb.set(mContactsHelper.getWritableDatabase());
-        }
+        final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
 
         final boolean callerIsSyncAdapter =
                 readBooleanQueryParameter(uri, ContactsContract.CALLER_IS_SYNCADAPTER, false);
@@ -2279,7 +2301,7 @@
         switch (match) {
             case SYNCSTATE:
             case PROFILE_SYNCSTATE:
-                id = mDbHelper.get().getSyncState().insert(mActiveDb.get(), values);
+                id = mDbHelper.get().getSyncState().insert(db, values);
                 break;
 
             case CONTACTS: {
@@ -2506,7 +2528,9 @@
             mValues.put(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DISABLED);
         }
 
-        long rawContactId = mActiveDb.get().insert(Tables.RAW_CONTACTS,
+        final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
+
+        long rawContactId = db.insert(Tables.RAW_CONTACTS,
                 RawContacts.CONTACT_ID, mValues);
         int aggregationMode = RawContacts.AGGREGATION_MODE_DEFAULT;
         if (mValues.containsKey(RawContacts.AGGREGATION_MODE)) {
@@ -2538,7 +2562,8 @@
     }
 
     private Long findGroupByRawContactId(String selection, long rawContactId) {
-        Cursor c = mActiveDb.get().query(Tables.GROUPS + "," + Tables.RAW_CONTACTS,
+        final SQLiteDatabase db = mDbHelper.get().getReadableDatabase();
+        Cursor c = db.query(Tables.GROUPS + "," + Tables.RAW_CONTACTS,
                 PROJECTION_GROUP_ID, selection,
                 new String[]{Long.toString(rawContactId)},
                 null /* groupBy */, null /* having */, null /* orderBy */);
@@ -2570,7 +2595,8 @@
         groupMembershipValues.put(GroupMembership.RAW_CONTACT_ID, rawContactId);
         groupMembershipValues.put(DataColumns.MIMETYPE_ID,
                 mDbHelper.get().getMimeTypeId(GroupMembership.CONTENT_ITEM_TYPE));
-        mActiveDb.get().insert(Tables.DATA, null, groupMembershipValues);
+        final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
+        db.insert(Tables.DATA, null, groupMembershipValues);
     }
 
     private void deleteDataGroupMembership(long rawContactId, long groupId) {
@@ -2578,7 +2604,8 @@
                 Long.toString(mDbHelper.get().getMimeTypeId(GroupMembership.CONTENT_ITEM_TYPE)),
                 Long.toString(groupId),
                 Long.toString(rawContactId)};
-        mActiveDb.get().delete(Tables.DATA, SELECTION_GROUPMEMBERSHIP_DATA, selectionArgs);
+        final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
+        db.delete(Tables.DATA, SELECTION_GROUPMEMBERSHIP_DATA, selectionArgs);
     }
 
     /**
@@ -2611,7 +2638,8 @@
         mValues.remove(Data.MIMETYPE);
 
         DataRowHandler rowHandler = getDataRowHandler(mimeType);
-        id = rowHandler.insert(mActiveDb.get(), mTransactionContext.get(), rawContactId, mValues);
+        final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
+        id = rowHandler.insert(db, mTransactionContext.get(), rawContactId, mValues);
         if (!callerIsSyncAdapter) {
             mTransactionContext.get().markRawContactDirty(rawContactId);
         }
@@ -2642,7 +2670,8 @@
         mValues.remove(RawContacts.ACCOUNT_TYPE);
 
         // Insert the new stream item.
-        id = mActiveDb.get().insert(Tables.STREAM_ITEMS, null, mValues);
+        final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
+        id = db.insert(Tables.STREAM_ITEMS, null, mValues);
         if (id == -1) {
             // Insertion failed.
             return 0;
@@ -2682,7 +2711,8 @@
             // Process the photo and store it.
             if (processStreamItemPhoto(mValues, false)) {
                 // Insert the stream item photo.
-                id = mActiveDb.get().insert(Tables.STREAM_ITEM_PHOTOS, null, mValues);
+                final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
+                id = db.insert(Tables.STREAM_ITEM_PHOTOS, null, mValues);
             }
         }
         return id;
@@ -2734,7 +2764,8 @@
      */
     private long lookupRawContactIdForStreamId(long streamItemId) {
         long rawContactId = -1;
-        Cursor c = mActiveDb.get().query(Tables.STREAM_ITEMS,
+        final SQLiteDatabase db = mDbHelper.get().getReadableDatabase();
+        Cursor c = db.query(Tables.STREAM_ITEMS,
                 new String[]{StreamItems.RAW_CONTACT_ID},
                 StreamItems._ID + "=?", new String[]{String.valueOf(streamItemId)},
                 null, null, null);
@@ -2786,7 +2817,8 @@
      */
     private long cleanUpOldStreamItems(long rawContactId, long insertedStreamItemId) {
         long postCleanupInsertedStreamId = insertedStreamItemId;
-        Cursor c = mActiveDb.get().query(Tables.STREAM_ITEMS, new String[]{StreamItems._ID},
+        final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
+        Cursor c = db.query(Tables.STREAM_ITEMS, new String[]{StreamItems._ID},
                 StreamItems.RAW_CONTACT_ID + "=?", new String[]{String.valueOf(rawContactId)},
                 null, null, StreamItems.TIMESTAMP + " DESC, " + StreamItems._ID + " DESC");
         try {
@@ -2802,7 +2834,7 @@
                         // The stream item just inserted is being deleted.
                         postCleanupInsertedStreamId = 0;
                     }
-                    deleteStreamItem(c.getLong(0));
+                    deleteStreamItem(db, c.getLong(0));
                     c.moveToPrevious();
                 }
             }
@@ -2818,6 +2850,8 @@
     private int deleteData(String selection, String[] selectionArgs, boolean callerIsSyncAdapter) {
         int count = 0;
 
+        final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
+
         // Note that the query will return data according to the access restrictions,
         // so we don't need to worry about deleting data we don't have permission to read.
         Uri dataUri = inProfileMode()
@@ -2830,7 +2864,7 @@
                 long rawContactId = c.getLong(DataRowHandler.DataDeleteQuery.RAW_CONTACT_ID);
                 String mimeType = c.getString(DataRowHandler.DataDeleteQuery.MIMETYPE);
                 DataRowHandler rowHandler = getDataRowHandler(mimeType);
-                count += rowHandler.delete(mActiveDb.get(), mTransactionContext.get(), c);
+                count += rowHandler.delete(db, mTransactionContext.get(), c);
                 if (!callerIsSyncAdapter) {
                     mTransactionContext.get().markRawContactDirty(rawContactId);
                 }
@@ -2847,6 +2881,8 @@
      */
     public int deleteData(long dataId, String[] allowedMimeTypes) {
 
+        final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
+
         // Note that the query will return data according to the access restrictions,
         // so we don't need to worry about deleting data we don't have permission to read.
         mSelectionArgs1[0] = String.valueOf(dataId);
@@ -2872,7 +2908,7 @@
                         + Lists.newArrayList(allowedMimeTypes));
             }
             DataRowHandler rowHandler = getDataRowHandler(mimeType);
-            return rowHandler.delete(mActiveDb.get(), mTransactionContext.get(), c);
+            return rowHandler.delete(db, mTransactionContext.get(), c);
         } finally {
             c.close();
         }
@@ -2907,12 +2943,14 @@
             mValues.put(Groups.DIRTY, 1);
         }
 
-        long result = mActiveDb.get().insert(Tables.GROUPS, Groups.TITLE, mValues);
+        final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
+
+        long result = db.insert(Tables.GROUPS, Groups.TITLE, mValues);
 
         if (!callerIsSyncAdapter && isFavoritesGroup) {
             // If the inserted group is a favorite group, add all starred raw contacts to it.
             mSelectionArgs1[0] = Long.toString(accountId);
-            Cursor c = mActiveDb.get().query(Tables.RAW_CONTACTS,
+            Cursor c = db.query(Tables.RAW_CONTACTS,
                     new String[]{RawContacts._ID, RawContacts.STARRED},
                     RawContactsColumns.CONCRETE_ACCOUNT_ID + "=?", mSelectionArgs1,
                     null, null, null);
@@ -2975,8 +3013,10 @@
             c.close();
         }
 
+        final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
+
         // If we didn't find a duplicate, we're fine to insert.
-        final long id = mActiveDb.get().insert(Tables.SETTINGS, null, values);
+        final long id = db.insert(Tables.SETTINGS, null, values);
 
         if (values.containsKey(Settings.UNGROUPED_VISIBLE)) {
             mVisibleTouched = true;
@@ -2993,6 +3033,8 @@
         final Integer protocol = values.getAsInteger(StatusUpdates.PROTOCOL);
         String customProtocol = null;
 
+        final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
+
         if (protocol != null && protocol == Im.PROTOCOL_CUSTOM) {
             customProtocol = values.getAsString(StatusUpdates.CUSTOM_PROTOCOL);
             if (TextUtils.isEmpty(customProtocol)) {
@@ -3068,7 +3110,7 @@
 
         Cursor cursor = null;
         try {
-            cursor = mActiveDb.get().query(DataContactsQuery.TABLE, DataContactsQuery.PROJECTION,
+            cursor = db.query(DataContactsQuery.TABLE, DataContactsQuery.PROJECTION,
                     mSb.toString(), mSelectionArgs.toArray(EMPTY_STRING_ARRAY), null, null,
                     Clauses.CONTACT_VISIBLE + " DESC, " + Data.RAW_CONTACT_ID);
             if (cursor.moveToFirst()) {
@@ -3110,7 +3152,7 @@
                     values.getAsString(StatusUpdates.CHAT_CAPABILITY));
 
             // Insert the presence update
-            mActiveDb.get().replace(Tables.PRESENCE, null, mValues);
+            db.replace(Tables.PRESENCE, null, mValues);
         }
 
 
@@ -3237,10 +3279,7 @@
                     "  selection=[" + selection + "]  args=" + Arrays.toString(selectionArgs));
         }
 
-        // Default active DB to the contacts DB if none has been set.
-        if (mActiveDb.get() == null) {
-            mActiveDb.set(mContactsHelper.getWritableDatabase());
-        }
+        final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
 
         flushTransactionalChanges();
         final boolean callerIsSyncAdapter =
@@ -3249,14 +3288,14 @@
         switch (match) {
             case SYNCSTATE:
             case PROFILE_SYNCSTATE:
-                return mDbHelper.get().getSyncState().delete(mActiveDb.get(), selection,
+                return mDbHelper.get().getSyncState().delete(db, selection,
                         selectionArgs);
 
             case SYNCSTATE_ID: {
                 String selectionWithId =
                         (SyncStateContract.Columns._ID + "=" + ContentUris.parseId(uri) + " ")
                         + (selection == null ? "" : " AND (" + selection + ")");
-                return mDbHelper.get().getSyncState().delete(mActiveDb.get(), selectionWithId,
+                return mDbHelper.get().getSyncState().delete(db, selectionWithId,
                         selectionArgs);
             }
 
@@ -3264,7 +3303,7 @@
                 String selectionWithId =
                         (SyncStateContract.Columns._ID + "=" + ContentUris.parseId(uri) + " ")
                         + (selection == null ? "" : " AND (" + selection + ")");
-                return mProfileHelper.getSyncState().delete(mActiveDb.get(), selectionWithId,
+                return mProfileHelper.getSyncState().delete(db, selectionWithId,
                         selectionArgs);
             }
 
@@ -3289,7 +3328,7 @@
                             "Missing a lookup key", uri));
                 }
                 final String lookupKey = pathSegments.get(2);
-                final long contactId = lookupContactIdByLookupKey(mActiveDb.get(), lookupKey);
+                final long contactId = lookupContactIdByLookupKey(db, lookupKey);
                 return deleteContact(contactId, callerIsSyncAdapter);
             }
 
@@ -3311,7 +3350,7 @@
                 args[0] = String.valueOf(contactId);
                 args[1] = Uri.encode(lookupKey);
                 lookupQb.appendWhere(Contacts._ID + "=? AND " + Contacts.LOOKUP_KEY + "=?");
-                Cursor c = query(mActiveDb.get(), lookupQb, null, selection, args, null, null,
+                Cursor c = query(db, lookupQb, null, selection, args, null, null,
                         null, null, null);
                 try {
                     if (c.getCount() == 1) {
@@ -3335,7 +3374,7 @@
             case PROFILE_RAW_CONTACTS: {
                 invalidateFastScrollingIndexCache();
                 int numDeletes = 0;
-                Cursor c = mActiveDb.get().query(Views.RAW_CONTACTS,
+                Cursor c = db.query(Views.RAW_CONTACTS,
                         new String[]{RawContacts._ID, RawContacts.CONTACT_ID},
                         appendAccountIdToSelection(uri, selection), selectionArgs,
                         null, null, null);
@@ -3388,7 +3427,7 @@
 
             case GROUPS: {
                 int numDeletes = 0;
-                Cursor c = mActiveDb.get().query(Views.GROUPS, Projections.ID,
+                Cursor c = db.query(Views.GROUPS, Projections.ID,
                         appendAccountIdToSelection(uri, selection), selectionArgs,
                         null, null, null);
                 try {
@@ -3464,21 +3503,22 @@
     }
 
     public int deleteGroup(Uri uri, long groupId, boolean callerIsSyncAdapter) {
+        final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
         mGroupIdCache.clear();
         final long groupMembershipMimetypeId = mDbHelper.get()
                 .getMimeTypeId(GroupMembership.CONTENT_ITEM_TYPE);
-        mActiveDb.get().delete(Tables.DATA, DataColumns.MIMETYPE_ID + "="
+        db.delete(Tables.DATA, DataColumns.MIMETYPE_ID + "="
                 + groupMembershipMimetypeId + " AND " + GroupMembership.GROUP_ROW_ID + "="
                 + groupId, null);
 
         try {
             if (callerIsSyncAdapter) {
-                return mActiveDb.get().delete(Tables.GROUPS, Groups._ID + "=" + groupId, null);
+                return db.delete(Tables.GROUPS, Groups._ID + "=" + groupId, null);
             } else {
                 mValues.clear();
                 mValues.put(Groups.DELETED, 1);
                 mValues.put(Groups.DIRTY, 1);
-                return mActiveDb.get().update(Tables.GROUPS, mValues, Groups._ID + "=" + groupId,
+                return db.update(Tables.GROUPS, mValues, Groups._ID + "=" + groupId,
                         null);
             }
         } finally {
@@ -3487,20 +3527,22 @@
     }
 
     private int deleteSettings(Uri uri, String selection, String[] selectionArgs) {
-        final int count = mActiveDb.get().delete(Tables.SETTINGS, selection, selectionArgs);
+        final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
+        final int count = db.delete(Tables.SETTINGS, selection, selectionArgs);
         mVisibleTouched = true;
         return count;
     }
 
     private int deleteContact(long contactId, boolean callerIsSyncAdapter) {
+        final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
         mSelectionArgs1[0] = Long.toString(contactId);
-        Cursor c = mActiveDb.get().query(Tables.RAW_CONTACTS, new String[]{RawContacts._ID},
+        Cursor c = db.query(Tables.RAW_CONTACTS, new String[]{RawContacts._ID},
                 RawContacts.CONTACT_ID + "=?", mSelectionArgs1,
                 null, null, null);
         try {
             while (c.moveToNext()) {
                 long rawContactId = c.getLong(0);
-                markRawContactAsDeleted(rawContactId, callerIsSyncAdapter);
+                markRawContactAsDeleted(db, rawContactId, callerIsSyncAdapter);
             }
         } finally {
             c.close();
@@ -3508,36 +3550,38 @@
 
         mProviderStatusUpdateNeeded = true;
 
-        return mActiveDb.get().delete(Tables.CONTACTS, Contacts._ID + "=" + contactId, null);
+        return db.delete(Tables.CONTACTS, Contacts._ID + "=" + contactId, null);
     }
 
     public int deleteRawContact(long rawContactId, long contactId, boolean callerIsSyncAdapter) {
         mAggregator.get().invalidateAggregationExceptionCache();
         mProviderStatusUpdateNeeded = true;
 
+        final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
+
         // Find and delete stream items associated with the raw contact.
-        Cursor c = mActiveDb.get().query(Tables.STREAM_ITEMS,
+        Cursor c = db.query(Tables.STREAM_ITEMS,
                 new String[]{StreamItems._ID},
                 StreamItems.RAW_CONTACT_ID + "=?", new String[]{String.valueOf(rawContactId)},
                 null, null, null);
         try {
             while (c.moveToNext()) {
-                deleteStreamItem(c.getLong(0));
+                deleteStreamItem(db, c.getLong(0));
             }
         } finally {
             c.close();
         }
 
         if (callerIsSyncAdapter || rawContactIsLocal(rawContactId)) {
-            mActiveDb.get().delete(Tables.PRESENCE,
+            db.delete(Tables.PRESENCE,
                     PresenceColumns.RAW_CONTACT_ID + "=" + rawContactId, null);
-            int count = mActiveDb.get().delete(Tables.RAW_CONTACTS,
+            int count = db.delete(Tables.RAW_CONTACTS,
                     RawContacts._ID + "=" + rawContactId, null);
             mAggregator.get().updateAggregateData(mTransactionContext.get(), contactId);
             return count;
         } else {
             mDbHelper.get().removeContactIfSingleton(rawContactId);
-            return markRawContactAsDeleted(rawContactId, callerIsSyncAdapter);
+            return markRawContactAsDeleted(db, rawContactId, callerIsSyncAdapter);
         }
     }
 
@@ -3545,7 +3589,8 @@
      * Returns whether the given raw contact ID is local (i.e. has no account associated with it).
      */
     private boolean rawContactIsLocal(long rawContactId) {
-        Cursor c = mActiveDb.get().query(Tables.RAW_CONTACTS, Projections.LITERAL_ONE,
+        final SQLiteDatabase db = mDbHelper.get().getReadableDatabase();
+        Cursor c = db.query(Tables.RAW_CONTACTS, Projections.LITERAL_ONE,
                 RawContactsColumns.CONCRETE_ID + "=? AND " +
                 RawContactsColumns.ACCOUNT_ID + "=" + Clauses.LOCAL_ACCOUNT_ID,
                 new String[] {String.valueOf(rawContactId)}, null, null, null);
@@ -3562,20 +3607,22 @@
       if (VERBOSE_LOGGING) {
           Log.v(TAG, "deleting data from status_updates for " + selection);
       }
-      mActiveDb.get().delete(Tables.STATUS_UPDATES, getWhereClauseForStatusUpdatesTable(selection),
+      final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
+      db.delete(Tables.STATUS_UPDATES, getWhereClauseForStatusUpdatesTable(selection),
           selectionArgs);
-      return mActiveDb.get().delete(Tables.PRESENCE, selection, selectionArgs);
+      return db.delete(Tables.PRESENCE, selection, selectionArgs);
     }
 
     private int deleteStreamItems(Uri uri, ContentValues values, String selection,
             String[] selectionArgs) {
+        final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
         int count = 0;
-        final Cursor c = mActiveDb.get().query(Views.STREAM_ITEMS, Projections.ID,
+        final Cursor c = db.query(Views.STREAM_ITEMS, Projections.ID,
                 selection, selectionArgs, null, null, null);
         try {
             c.moveToPosition(-1);
             while (c.moveToNext()) {
-                count += deleteStreamItem(c.getLong(0));
+                count += deleteStreamItem(db, c.getLong(0));
             }
         } finally {
             c.close();
@@ -3583,25 +3630,28 @@
         return count;
     }
 
-    private int deleteStreamItem(long streamItemId) {
+    private int deleteStreamItem(SQLiteDatabase db, long streamItemId) {
         deleteStreamItemPhotos(streamItemId);
-        return mActiveDb.get().delete(Tables.STREAM_ITEMS, StreamItems._ID + "=?",
+        return db.delete(Tables.STREAM_ITEMS, StreamItems._ID + "=?",
                 new String[]{String.valueOf(streamItemId)});
     }
 
     private int deleteStreamItemPhotos(Uri uri, ContentValues values, String selection,
             String[] selectionArgs) {
-        return mActiveDb.get().delete(Tables.STREAM_ITEM_PHOTOS, selection, selectionArgs);
+        final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
+        return db.delete(Tables.STREAM_ITEM_PHOTOS, selection, selectionArgs);
     }
 
     private int deleteStreamItemPhotos(long streamItemId) {
+        final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
         // Note that this does not enforce the modifying account.
-        return mActiveDb.get().delete(Tables.STREAM_ITEM_PHOTOS,
+        return db.delete(Tables.STREAM_ITEM_PHOTOS,
                 StreamItemPhotos.STREAM_ITEM_ID + "=?",
                 new String[]{String.valueOf(streamItemId)});
     }
 
-    private int markRawContactAsDeleted(long rawContactId, boolean callerIsSyncAdapter) {
+    private int markRawContactAsDeleted(SQLiteDatabase db, long rawContactId,
+            boolean callerIsSyncAdapter) {
         mSyncToNetwork = true;
 
         mValues.clear();
@@ -3610,11 +3660,11 @@
         mValues.put(RawContactsColumns.AGGREGATION_NEEDED, 1);
         mValues.putNull(RawContacts.CONTACT_ID);
         mValues.put(RawContacts.DIRTY, 1);
-        return updateRawContact(rawContactId, mValues, callerIsSyncAdapter);
+        return updateRawContact(db, rawContactId, mValues, callerIsSyncAdapter);
     }
 
     private int deleteDataUsage() {
-        final SQLiteDatabase db = mActiveDb.get();
+        final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
         db.execSQL("UPDATE " + Tables.RAW_CONTACTS + " SET " +
                 Contacts.TIMES_CONTACTED + "=0," +
                 Contacts.LAST_TIME_CONTACTED + "=NULL"
@@ -3637,10 +3687,7 @@
                     "  values=[" + values + "]");
         }
 
-        // Default active DB to the contacts DB if none has been set.
-        if (mActiveDb.get() == null) {
-            mActiveDb.set(mContactsHelper.getWritableDatabase());
-        }
+        final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
 
         int count = 0;
 
@@ -3657,7 +3704,7 @@
         switch(match) {
             case SYNCSTATE:
             case PROFILE_SYNCSTATE:
-                return mDbHelper.get().getSyncState().update(mActiveDb.get(), values,
+                return mDbHelper.get().getSyncState().update(db, values,
                         appendAccountToSelection(uri, selection), selectionArgs);
 
             case SYNCSTATE_ID: {
@@ -3665,7 +3712,7 @@
                 String selectionWithId =
                         (SyncStateContract.Columns._ID + "=" + ContentUris.parseId(uri) + " ")
                         + (selection == null ? "" : " AND (" + selection + ")");
-                return mDbHelper.get().getSyncState().update(mActiveDb.get(), values,
+                return mDbHelper.get().getSyncState().update(db, values,
                         selectionWithId, selectionArgs);
             }
 
@@ -3674,7 +3721,7 @@
                 String selectionWithId =
                         (SyncStateContract.Columns._ID + "=" + ContentUris.parseId(uri) + " ")
                         + (selection == null ? "" : " AND (" + selection + ")");
-                return mProfileHelper.getSyncState().update(mActiveDb.get(), values,
+                return mProfileHelper.getSyncState().update(db, values,
                         selectionWithId, selectionArgs);
             }
 
@@ -3687,7 +3734,8 @@
 
             case CONTACTS_ID: {
                 invalidateFastScrollingIndexCache();
-                count = updateContactOptions(ContentUris.parseId(uri), values, callerIsSyncAdapter);
+                count = updateContactOptions(db, ContentUris.parseId(uri), values,
+                        callerIsSyncAdapter);
                 break;
             }
 
@@ -3701,8 +3749,8 @@
                             "Missing a lookup key", uri));
                 }
                 final String lookupKey = pathSegments.get(2);
-                final long contactId = lookupContactIdByLookupKey(mActiveDb.get(), lookupKey);
-                count = updateContactOptions(contactId, values, callerIsSyncAdapter);
+                final long contactId = lookupContactIdByLookupKey(db, lookupKey);
+                count = updateContactOptions(db, contactId, values, callerIsSyncAdapter);
                 break;
             }
 
@@ -3790,7 +3838,7 @@
             }
 
             case AGGREGATION_EXCEPTIONS: {
-                count = updateAggregationException(mActiveDb.get(), values);
+                count = updateAggregationException(db, values);
                 break;
             }
 
@@ -3875,12 +3923,13 @@
 
     private int updateStatusUpdate(Uri uri, ContentValues values, String selection,
         String[] selectionArgs) {
+        final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
         // update status_updates table, if status is provided
         // TODO should account type/name be appended to the where clause?
         int updateCount = 0;
         ContentValues settableValues = getSettableColumnsForStatusUpdatesTable(values);
         if (settableValues.size() > 0) {
-          updateCount = mActiveDb.get().update(Tables.STATUS_UPDATES,
+          updateCount = db.update(Tables.STATUS_UPDATES,
                     settableValues,
                     getWhereClauseForStatusUpdatesTable(selection),
                     selectionArgs);
@@ -3889,7 +3938,7 @@
         // now update the Presence table
         settableValues = getSettableColumnsForPresenceTable(values);
         if (settableValues.size() > 0) {
-          updateCount = mActiveDb.get().update(Tables.PRESENCE, settableValues,
+          updateCount = db.update(Tables.PRESENCE, settableValues,
                     selection, selectionArgs);
         }
         // TODO updateCount is not entirely a valid count of updated rows because 2 tables could
@@ -3906,8 +3955,10 @@
         values.remove(RawContacts.ACCOUNT_NAME);
         values.remove(RawContacts.ACCOUNT_TYPE);
 
+        final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
+
         // If there's been no exception, the update should be fine.
-        return mActiveDb.get().update(Tables.STREAM_ITEMS, values, selection, selectionArgs);
+        return db.update(Tables.STREAM_ITEMS, values, selection, selectionArgs);
     }
 
     private int updateStreamItemPhotos(Uri uri, ContentValues values, String selection,
@@ -3920,10 +3971,12 @@
         values.remove(RawContacts.ACCOUNT_NAME);
         values.remove(RawContacts.ACCOUNT_TYPE);
 
+        final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
+
         // Process the photo (since we're updating, it's valid for the photo to not be present).
         if (processStreamItemPhoto(values, true)) {
             // If there's been no exception, the update should be fine.
-            return mActiveDb.get().update(Tables.STREAM_ITEM_PHOTOS, values, selection,
+            return db.update(Tables.STREAM_ITEM_PHOTOS, values, selection,
                     selectionArgs);
         }
         return 0;
@@ -3983,7 +4036,7 @@
             String[] selectionArgs, boolean callerIsSyncAdapter) {
         mGroupIdCache.clear();
 
-        final SQLiteDatabase db = mActiveDb.get();
+        final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
         final ContactsDatabaseHelper dbHelper = mDbHelper.get();
 
         final ContentValues updatedValues = new ContentValues();
@@ -4068,7 +4121,8 @@
 
     private int updateSettings(Uri uri, ContentValues values, String selection,
             String[] selectionArgs) {
-        final int count = mActiveDb.get().update(Tables.SETTINGS, values, selection, selectionArgs);
+        final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
+        final int count = db.update(Tables.SETTINGS, values, selection, selectionArgs);
         if (values.containsKey(Settings.UNGROUPED_VISIBLE)) {
             mVisibleTouched = true;
         }
@@ -4088,13 +4142,14 @@
         }
 
         int count = 0;
-        Cursor cursor = mActiveDb.get().query(Views.RAW_CONTACTS,
+        final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
+        Cursor cursor = db.query(Views.RAW_CONTACTS,
                 Projections.ID, selection,
                 selectionArgs, null, null, null);
         try {
             while (cursor.moveToNext()) {
                 long rawContactId = cursor.getLong(0);
-                updateRawContact(rawContactId, values, callerIsSyncAdapter);
+                updateRawContact(db, rawContactId, values, callerIsSyncAdapter);
                 count++;
             }
         } finally {
@@ -4104,7 +4159,7 @@
         return count;
     }
 
-    private int updateRawContact(long rawContactId, ContentValues values,
+    private int updateRawContact(SQLiteDatabase db, long rawContactId, ContentValues values,
             boolean callerIsSyncAdapter) {
         final String selection = RawContactsColumns.CONCRETE_ID + " = ?";
         mSelectionArgs1[0] = Long.toString(rawContactId);
@@ -4127,7 +4182,7 @@
         String oldDataSet = null;
 
         if (requestUndoDelete || isAccountChanging) {
-            Cursor cursor = mActiveDb.get().query(RawContactsQuery.TABLE, RawContactsQuery.COLUMNS,
+            Cursor cursor = db.query(RawContactsQuery.TABLE, RawContactsQuery.COLUMNS,
                     selection, mSelectionArgs1, null, null, null);
             try {
                 if (cursor.moveToFirst()) {
@@ -4172,7 +4227,7 @@
                     ContactsContract.RawContacts.AGGREGATION_MODE_DEFAULT);
         }
 
-        int count = mActiveDb.get().update(Tables.RAW_CONTACTS, values, selection, mSelectionArgs1);
+        int count = db.update(Tables.RAW_CONTACTS, values, selection, mSelectionArgs1);
         if (count != 0) {
             if (values.containsKey(RawContacts.AGGREGATION_MODE)) {
                 int aggregationMode = values.getAsInteger(RawContacts.AGGREGATION_MODE);
@@ -4195,7 +4250,7 @@
                 // If it is starred, add a group membership, if one doesn't already exist
                 // otherwise delete any matching group memberships.
                 if (!callerIsSyncAdapter && isAccountChanging) {
-                    boolean starred = 0 != DatabaseUtils.longForQuery(mActiveDb.get(),
+                    boolean starred = 0 != DatabaseUtils.longForQuery(db,
                             SELECTION_STARRED_FROM_RAW_CONTACTS,
                             new String[]{Long.toString(rawContactId)});
                     updateFavoritesMembership(rawContactId, starred);
@@ -4209,7 +4264,7 @@
             }
 
             if (values.containsKey(RawContacts.SOURCE_ID)) {
-                mAggregator.get().updateLookupKeyForRawContact(mActiveDb.get(), rawContactId);
+                mAggregator.get().updateLookupKeyForRawContact(db, rawContactId);
             }
             if (values.containsKey(RawContacts.NAME_VERIFIED)) {
 
@@ -4218,7 +4273,7 @@
                 if (values.getAsInteger(RawContacts.NAME_VERIFIED) != 0) {
                     mDbHelper.get().resetNameVerifiedForOtherRawContacts(rawContactId);
                 }
-                mAggregator.get().updateDisplayNameForRawContact(mActiveDb.get(), rawContactId);
+                mAggregator.get().updateDisplayNameForRawContact(db, rawContactId);
             }
             if (requestUndoDelete && previousDeleted == 1) {
                 // Note before the accounts refactoring, we used to use the *old* account here,
@@ -4273,10 +4328,12 @@
             return 0;
         }
 
+        final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
+
         final String mimeType = c.getString(DataRowHandler.DataUpdateQuery.MIMETYPE);
         DataRowHandler rowHandler = getDataRowHandler(mimeType);
         boolean updated =
-                rowHandler.update(mActiveDb.get(), mTransactionContext.get(), values, c,
+                rowHandler.update(db, mTransactionContext.get(), values, c,
                         callerIsSyncAdapter);
         if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
             scheduleBackgroundTask(BACKGROUND_TASK_CLEANUP_PHOTOS);
@@ -4287,13 +4344,15 @@
     private int updateContactOptions(ContentValues values, String selection,
             String[] selectionArgs, boolean callerIsSyncAdapter) {
         int count = 0;
-        Cursor cursor = mActiveDb.get().query(Views.CONTACTS,
+        final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
+
+        Cursor cursor = db.query(Views.CONTACTS,
                 new String[] { Contacts._ID }, selection, selectionArgs, null, null, null);
         try {
             while (cursor.moveToNext()) {
                 long contactId = cursor.getLong(0);
 
-                updateContactOptions(contactId, values, callerIsSyncAdapter);
+                updateContactOptions(db, contactId, values, callerIsSyncAdapter);
                 count++;
             }
         } finally {
@@ -4303,7 +4362,7 @@
         return count;
     }
 
-    private int updateContactOptions(long contactId, ContentValues values,
+    private int updateContactOptions(SQLiteDatabase db, long contactId, ContentValues values,
             boolean callerIsSyncAdapter) {
 
         mValues.clear();
@@ -4329,11 +4388,11 @@
         }
 
         mSelectionArgs1[0] = String.valueOf(contactId);
-        mActiveDb.get().update(Tables.RAW_CONTACTS, mValues, RawContacts.CONTACT_ID + "=?"
+        db.update(Tables.RAW_CONTACTS, mValues, RawContacts.CONTACT_ID + "=?"
                 + " AND " + RawContacts.RAW_CONTACT_IS_READ_ONLY + "=0", mSelectionArgs1);
 
         if (mValues.containsKey(RawContacts.STARRED) && !callerIsSyncAdapter) {
-            Cursor cursor = mActiveDb.get().query(Views.RAW_CONTACTS,
+            Cursor cursor = db.query(Views.RAW_CONTACTS,
                     new String[] { RawContacts._ID }, RawContacts.CONTACT_ID + "=?",
                     mSelectionArgs1, null, null, null);
             try {
@@ -4361,13 +4420,13 @@
         ContactsDatabaseHelper.copyLongValue(mValues, RawContacts.STARRED,
                 values, Contacts.STARRED);
 
-        int rslt = mActiveDb.get().update(Tables.CONTACTS, mValues, Contacts._ID + "=?",
+        int rslt = db.update(Tables.CONTACTS, mValues, Contacts._ID + "=?",
                 mSelectionArgs1);
 
         if (values.containsKey(Contacts.LAST_TIME_CONTACTED) &&
                 !values.containsKey(Contacts.TIMES_CONTACTED)) {
-            mActiveDb.get().execSQL(UPDATE_TIMES_CONTACTED_CONTACTS_TABLE, mSelectionArgs1);
-            mActiveDb.get().execSQL(UPDATE_TIMES_CONTACTED_RAWCONTACTS_TABLE, mSelectionArgs1);
+            db.execSQL(UPDATE_TIMES_CONTACTED_CONTACTS_TABLE, mSelectionArgs1);
+            db.execSQL(UPDATE_TIMES_CONTACTED_RAWCONTACTS_TABLE, mSelectionArgs1);
         }
         return rslt;
     }
@@ -4416,6 +4475,7 @@
         return 1;
     }
 
+    @Override
     public void onAccountsUpdated(Account[] accounts) {
         scheduleBackgroundTask(BACKGROUND_TASK_UPDATE_ACCOUNTS);
     }
@@ -4499,7 +4559,6 @@
 
         final ContactsDatabaseHelper dbHelper = mDbHelper.get();
         final SQLiteDatabase db = dbHelper.getWritableDatabase();
-        mActiveDb.set(db);
         db.beginTransaction();
 
         // WARNING: This method can be run in either contacts mode or profile mode.  It is
@@ -4716,7 +4775,6 @@
 
         // Otherwise proceed with a normal query against the contacts DB.
         switchToContactMode();
-        mActiveDb.set(mContactsHelper.getReadableDatabase());
         String directory = getQueryParameter(uri, ContactsContract.DIRECTORY_PARAM_KEY);
         if (directory == null) {
             return addSnippetExtrasToCursor(uri,
@@ -4868,10 +4926,7 @@
             String[] selectionArgs, String sortOrder, final long directoryId,
             final CancellationSignal cancellationSignal) {
 
-        // Default active DB to the contacts DB if none has been set.
-        if (mActiveDb.get() == null) {
-            mActiveDb.set(mContactsHelper.getReadableDatabase());
-        }
+        final SQLiteDatabase db = mDbHelper.get().getReadableDatabase();
 
         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
         String groupBy = null;
@@ -4886,7 +4941,7 @@
         switch (match) {
             case SYNCSTATE:
             case PROFILE_SYNCSTATE:
-                return mDbHelper.get().getSyncState().query(mActiveDb.get(), projection, selection,
+                return mDbHelper.get().getSyncState().query(db, projection, selection,
                         selectionArgs, sortOrder);
 
             case CONTACTS: {
@@ -4918,7 +4973,7 @@
                     SQLiteQueryBuilder lookupQb = new SQLiteQueryBuilder();
                     setTablesAndProjectionMapForContacts(lookupQb, uri, projection);
 
-                    Cursor c = queryWithContactIdAndLookupKey(lookupQb, mActiveDb.get(), uri,
+                    Cursor c = queryWithContactIdAndLookupKey(lookupQb, db, uri,
                             projection, selection, selectionArgs, sortOrder, groupBy, limit,
                             Contacts._ID, contactId, Contacts.LOOKUP_KEY, lookupKey,
                             cancellationSignal);
@@ -4929,7 +4984,7 @@
 
                 setTablesAndProjectionMapForContacts(qb, uri, projection);
                 selectionArgs = insertSelectionArg(selectionArgs,
-                        String.valueOf(lookupContactIdByLookupKey(mActiveDb.get(), lookupKey)));
+                        String.valueOf(lookupContactIdByLookupKey(db, lookupKey)));
                 qb.appendWhere(Contacts._ID + "=?");
                 break;
             }
@@ -4953,7 +5008,7 @@
                         lookupQb.appendWhere(" AND " + Data._ID + "=" + Contacts.PHOTO_ID);
                     }
                     lookupQb.appendWhere(" AND ");
-                    Cursor c = queryWithContactIdAndLookupKey(lookupQb, mActiveDb.get(), uri,
+                    Cursor c = queryWithContactIdAndLookupKey(lookupQb, db, uri,
                             projection, selection, selectionArgs, sortOrder, groupBy, limit,
                             Data.CONTACT_ID, contactId, Data.LOOKUP_KEY, lookupKey,
                             cancellationSignal);
@@ -4965,7 +5020,7 @@
                 }
 
                 setTablesAndProjectionMapForData(qb, uri, projection, false);
-                long contactId = lookupContactIdByLookupKey(mActiveDb.get(), lookupKey);
+                long contactId = lookupContactIdByLookupKey(db, lookupKey);
                 selectionArgs = insertSelectionArg(selectionArgs,
                         String.valueOf(contactId));
                 if (match == CONTACTS_LOOKUP_PHOTO || match == CONTACTS_LOOKUP_ID_PHOTO) {
@@ -4996,7 +5051,7 @@
                     long contactId = Long.parseLong(pathSegments.get(3));
                     SQLiteQueryBuilder lookupQb = new SQLiteQueryBuilder();
                     setTablesAndProjectionMapForStreamItems(lookupQb);
-                    Cursor c = queryWithContactIdAndLookupKey(lookupQb, mActiveDb.get(), uri,
+                    Cursor c = queryWithContactIdAndLookupKey(lookupQb, db, uri,
                             projection, selection, selectionArgs, sortOrder, groupBy, limit,
                             StreamItems.CONTACT_ID, contactId,
                             StreamItems.CONTACT_LOOKUP_KEY, lookupKey,
@@ -5007,7 +5062,7 @@
                 }
 
                 setTablesAndProjectionMapForStreamItems(qb);
-                long contactId = lookupContactIdByLookupKey(mActiveDb.get(), lookupKey);
+                long contactId = lookupContactIdByLookupKey(db, lookupKey);
                 selectionArgs = insertSelectionArg(selectionArgs, String.valueOf(contactId));
                 qb.appendWhere(RawContacts.CONTACT_ID + "=?");
                 break;
@@ -5015,7 +5070,7 @@
 
             case CONTACTS_AS_VCARD: {
                 final String lookupKey = Uri.encode(uri.getPathSegments().get(2));
-                long contactId = lookupContactIdByLookupKey(mActiveDb.get(), lookupKey);
+                long contactId = lookupContactIdByLookupKey(db, lookupKey);
                 qb.setTables(Views.CONTACTS);
                 qb.setProjectionMap(sContactsVCardProjectionMap);
                 selectionArgs = insertSelectionArg(selectionArgs,
@@ -5027,7 +5082,7 @@
             case CONTACTS_AS_MULTI_VCARD: {
                 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");
                 String currentDateString = dateFormat.format(new Date()).toString();
-                return mActiveDb.get().rawQuery(
+                return db.rawQuery(
                     "SELECT" +
                     " 'vcards_' || ? || '.vcf' AS " + OpenableColumns.DISPLAY_NAME + "," +
                     " NULL AS " + OpenableColumns.SIZE,
@@ -5173,7 +5228,7 @@
                     System.arraycopy(selectionArgs, 0, doubledSelectionArgs, length, length);
                 }
 
-                Cursor cursor = mActiveDb.get().rawQuery(unionQuery, doubledSelectionArgs);
+                Cursor cursor = db.rawQuery(unionQuery, doubledSelectionArgs);
                 if (cursor != null) {
                     cursor.setNotificationUri(getContext().getContentResolver(),
                             ContactsContract.AUTHORITY_URI);
@@ -5262,7 +5317,7 @@
                     setTablesAndProjectionMapForEntities(lookupQb, uri, projection);
                     lookupQb.appendWhere(" AND ");
 
-                    Cursor c = queryWithContactIdAndLookupKey(lookupQb, mActiveDb.get(), uri,
+                    Cursor c = queryWithContactIdAndLookupKey(lookupQb, db, uri,
                             projection, selection, selectionArgs, sortOrder, groupBy, limit,
                             Contacts.Entity.CONTACT_ID, contactId,
                             Contacts.Entity.LOOKUP_KEY, lookupKey,
@@ -5274,7 +5329,7 @@
 
                 setTablesAndProjectionMapForEntities(qb, uri, projection);
                 selectionArgs = insertSelectionArg(selectionArgs,
-                        String.valueOf(lookupContactIdByLookupKey(mActiveDb.get(), lookupKey)));
+                        String.valueOf(lookupContactIdByLookupKey(db, lookupKey)));
                 qb.appendWhere(" AND " + Contacts.Entity.CONTACT_ID + "=?");
                 break;
             }
@@ -5734,7 +5789,7 @@
                     // were returned, fall back to doing a match of the trailing 7 digits.
                     qb.setStrict(true);
                     boolean foundResult = false;
-                    Cursor cursor = query(mActiveDb.get(), qb, projection, selection, selectionArgs,
+                    Cursor cursor = query(db, qb, projection, selection, selectionArgs,
                             sortOrder, groupBy, null, limit, cancellationSignal);
                     try {
                         if (cursor.getCount() > 0) {
@@ -5867,7 +5922,7 @@
 
             case SEARCH_SUGGESTIONS: {
                 return mGlobalSearchSupport.handleSearchSuggestionsQuery(
-                        mActiveDb.get(), uri, projection, limit);
+                        db, uri, projection, limit);
             }
 
             case SEARCH_SHORTCUT: {
@@ -5875,7 +5930,7 @@
                 String filter = getQueryParameter(
                         uri, SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA);
                 return mGlobalSearchSupport.handleSearchShortcutRefresh(
-                        mActiveDb.get(), projection, lookupKey, filter);
+                        db, projection, lookupKey, filter);
             }
 
             case RAW_CONTACT_ENTITIES:
@@ -5925,11 +5980,11 @@
         qb.setStrict(true);
 
         Cursor cursor =
-                query(mActiveDb.get(), qb, projection, selection, selectionArgs, sortOrder, groupBy,
+                query(db, qb, projection, selection, selectionArgs, sortOrder, groupBy,
                 having, limit, cancellationSignal);
 
         if (readBooleanQueryParameter(uri, ContactCounts.ADDRESS_BOOK_INDEX_EXTRAS, false)) {
-            bundleFastScrollingIndexExtras(cursor, uri, mActiveDb.get(), qb, selection,
+            bundleFastScrollingIndexExtras(cursor, uri, db, qb, selection,
                     selectionArgs, sortOrder, addressBookIndexerCountExpression,
                     cancellationSignal);
         }
@@ -5990,10 +6045,6 @@
     }
 
     private void invalidateFastScrollingIndexCache() {
-        if (VERBOSE_LOGGING) {
-            Log.v(TAG, "invalidatemFastScrollingIndexCache");
-        }
-
         // FastScrollingIndexCache is thread-safe, no need to synchronize here.
         mFastScrollingIndexCache.invalidate();
     }
@@ -7078,20 +7129,15 @@
     public AssetFileDescriptor openAssetFileLocal(Uri uri, String mode)
             throws FileNotFoundException {
 
-        // Default active DB to the contacts DB if none has been set.
-        if (mActiveDb.get() == null) {
-            if (mode.equals("r")) {
-                mActiveDb.set(mContactsHelper.getReadableDatabase());
-            } else {
-                mActiveDb.set(mContactsHelper.getWritableDatabase());
-            }
-        }
+        final boolean writing = mode.contains("w");
+
+        final SQLiteDatabase db = mDbHelper.get().getDatabase(writing);
 
         int match = sUriMatcher.match(uri);
         switch (match) {
             case CONTACTS_ID_PHOTO: {
                 long contactId = Long.parseLong(uri.getPathSegments().get(1));
-                return openPhotoAssetFile(mActiveDb.get(), uri, mode,
+                return openPhotoAssetFile(db, uri, mode,
                         Data._ID + "=" + Contacts.PHOTO_ID + " AND " +
                                 RawContacts.CONTACT_ID + "=?",
                         new String[]{String.valueOf(contactId)});
@@ -7103,7 +7149,7 @@
                             "Display photos retrieved by contact ID can only be read.");
                 }
                 long contactId = Long.parseLong(uri.getPathSegments().get(1));
-                Cursor c = mActiveDb.get().query(Tables.CONTACTS,
+                Cursor c = db.query(Tables.CONTACTS,
                         new String[]{Contacts.PHOTO_FILE_ID},
                         Contacts._ID + "=?", new String[]{String.valueOf(contactId)},
                         null, null, null);
@@ -7125,7 +7171,7 @@
                     throw new IllegalArgumentException(
                             "Display photos retrieved by contact ID can only be read.");
                 }
-                Cursor c = mActiveDb.get().query(Tables.CONTACTS,
+                Cursor c = db.query(Tables.CONTACTS,
                         new String[]{Contacts.PHOTO_FILE_ID}, null, null, null, null, null);
                 try {
                     if (c.moveToFirst()) {
@@ -7163,7 +7209,7 @@
                     long contactId = Long.parseLong(pathSegments.get(3));
                     SQLiteQueryBuilder lookupQb = new SQLiteQueryBuilder();
                     setTablesAndProjectionMapForContacts(lookupQb, uri, projection);
-                    Cursor c = queryWithContactIdAndLookupKey(lookupQb, mActiveDb.get(), uri,
+                    Cursor c = queryWithContactIdAndLookupKey(lookupQb, db, uri,
                             projection, null, null, null, null, null,
                             Contacts._ID, contactId, Contacts.LOOKUP_KEY, lookupKey, null);
                     if (c != null) {
@@ -7175,7 +7221,7 @@
                                 return openDisplayPhotoForRead(photoFileId);
                             } else {
                                 long photoId = c.getLong(c.getColumnIndex(Contacts.PHOTO_ID));
-                                return openPhotoAssetFile(mActiveDb.get(), uri, mode,
+                                return openPhotoAssetFile(db, uri, mode,
                                         Data._ID + "=?", new String[]{String.valueOf(photoId)});
                             }
                         } finally {
@@ -7186,8 +7232,8 @@
 
                 SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
                 setTablesAndProjectionMapForContacts(qb, uri, projection);
-                long contactId = lookupContactIdByLookupKey(mActiveDb.get(), lookupKey);
-                Cursor c = qb.query(mActiveDb.get(), projection, Contacts._ID + "=?",
+                long contactId = lookupContactIdByLookupKey(db, lookupKey);
+                Cursor c = qb.query(db, projection, Contacts._ID + "=?",
                         new String[]{String.valueOf(contactId)}, null, null, null);
                 try {
                     c.moveToFirst();
@@ -7196,7 +7242,7 @@
                         return openDisplayPhotoForRead(photoFileId);
                     } else {
                         long photoId = c.getLong(c.getColumnIndex(Contacts.PHOTO_ID));
-                        return openPhotoAssetFile(mActiveDb.get(), uri, mode,
+                        return openPhotoAssetFile(db, uri, mode,
                                 Data._ID + "=?", new String[]{String.valueOf(photoId)});
                     }
                 } finally {
@@ -7213,7 +7259,7 @@
                 String[] projection = new String[]{Data._ID, Photo.PHOTO_FILE_ID};
                 setTablesAndProjectionMapForData(qb, uri, projection, false);
                 long photoMimetypeId = mDbHelper.get().getMimeTypeId(Photo.CONTENT_ITEM_TYPE);
-                Cursor c = qb.query(mActiveDb.get(), projection,
+                Cursor c = qb.query(db, projection,
                         Data.RAW_CONTACT_ID + "=? AND " + DataColumns.MIMETYPE_ID + "=?",
                         new String[]{String.valueOf(rawContactId), String.valueOf(photoMimetypeId)},
                         null, null, Data.IS_PRIMARY + " DESC");
@@ -7251,7 +7297,7 @@
             case DATA_ID: {
                 long dataId = Long.parseLong(uri.getPathSegments().get(1));
                 long photoMimetypeId = mDbHelper.get().getMimeTypeId(Photo.CONTENT_ITEM_TYPE);
-                return openPhotoAssetFile(mActiveDb.get(), uri, mode,
+                return openPhotoAssetFile(db, uri, mode,
                         Data._ID + "=? AND " + DataColumns.MIMETYPE_ID + "=" + photoMimetypeId,
                         new String[]{String.valueOf(dataId)});
             }
@@ -7290,7 +7336,7 @@
                         inBuilder.append(",");
                     }
                     // TODO: Figure out what to do if the profile contact is in the list.
-                    long contactId = lookupContactIdByLookupKey(mActiveDb.get(), lookupKey);
+                    long contactId = lookupContactIdByLookupKey(db, lookupKey);
                     inBuilder.append(contactId);
                     index++;
                 }
@@ -7955,10 +8001,9 @@
             SQLiteDatabase db = null;
             boolean success = false;
             try {
-                // Re-aggregation os only for the contacts DB.
+                // Re-aggregation is only for the contacts DB.
                 switchToContactMode();
                 db = mContactsHelper.getWritableDatabase();
-                mActiveDb.set(db);
 
                 // Start the actual process.
                 db.beginTransaction();
@@ -8038,7 +8083,7 @@
         }
         rawContactIdSelect.append(")");
 
-        final SQLiteDatabase db = mActiveDb.get();
+        final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
 
         mSelectionArgs1[0] = String.valueOf(currentTimeMillis);
 
@@ -8080,7 +8125,7 @@
     /* package */ int updateDataUsageStat(
             List<Long> dataIds, String type, long currentTimeMillis) {
 
-        final SQLiteDatabase db = mActiveDb.get();
+        final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
 
         final String typeString = String.valueOf(getDataUsageFeedbackType(type, null));
         final String currentTimeMillisString = String.valueOf(currentTimeMillis);
@@ -8253,4 +8298,15 @@
         }
         throw new IllegalArgumentException("Invalid usage type " + type);
     }
+
+    /** Use only for debug logging */
+    @Override
+    public String toString() {
+        return "ContactsProvider2";
+    }
+
+    @NeededForTesting
+    public void switchToProfileModeForTest() {
+        switchToProfileMode();
+    }
 }
diff --git a/src/com/android/providers/contacts/ContactsTransaction.java b/src/com/android/providers/contacts/ContactsTransaction.java
index 7a92cae..c6c11d9 100644
--- a/src/com/android/providers/contacts/ContactsTransaction.java
+++ b/src/com/android/providers/contacts/ContactsTransaction.java
@@ -16,11 +16,12 @@
 
 package com.android.providers.contacts;
 
-import com.google.android.collect.Lists;
-import com.google.android.collect.Maps;
-
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteTransactionListener;
+import android.util.Log;
+
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
 
 import java.util.List;
 import java.util.Map;
@@ -40,13 +41,16 @@
 
     /**
      * The list of databases that have been enlisted in this transaction.
+     *
+     * Note we insert elements to the head of the list, so that we endTransaction() in the reverse
+     * order.
      */
-    private List<SQLiteDatabase> mDatabasesForTransaction;
+    private final List<SQLiteDatabase> mDatabasesForTransaction;
 
     /**
      * The mapping of tags to databases involved in this transaction.
      */
-    private Map<String, SQLiteDatabase> mDatabaseTagMap;
+    private final Map<String, SQLiteDatabase> mDatabaseTagMap;
 
     /**
      * Whether any actual changes have been made successfully in this transaction.
@@ -97,8 +101,16 @@
      */
     public void startTransactionForDb(SQLiteDatabase db, String tag,
             SQLiteTransactionListener listener) {
+        if (AbstractContactsProvider.ENABLE_TRANSACTION_LOG) {
+            Log.i(AbstractContactsProvider.TAG, "startTransactionForDb: db=" + db.getPath() +
+                    "  tag=" + tag + "  listener=" + listener +
+                    "  startTransaction=" + !hasDbInTransaction(tag),
+                    new RuntimeException("startTransactionForDb"));
+        }
         if (!hasDbInTransaction(tag)) {
-            mDatabasesForTransaction.add(db);
+            // Insert a new db into the head of the list, so that we'll endTransaction() in
+            // the reverse order.
+            mDatabasesForTransaction.add(0, db);
             mDatabaseTagMap.put(tag, db);
             if (listener != null) {
                 db.beginTransactionWithListener(listener);
@@ -154,13 +166,33 @@
     }
 
     /**
+     * @return the tag for a database.  Only intended to be used for logging.
+     */
+    private String getTagForDb(SQLiteDatabase db) {
+        for (String tag : mDatabaseTagMap.keySet()) {
+            if (db == mDatabaseTagMap.get(tag)) {
+                return tag;
+            }
+        }
+        return null;
+    }
+
+    /**
      * Completes the transaction, ending the DB transactions for all associated databases.
      * @param callerIsBatch Whether this is being performed in the context of a batch operation.
      *     If it is not, and the transaction is marked as batch, this call is a no-op.
      */
     public void finish(boolean callerIsBatch) {
+        if (AbstractContactsProvider.ENABLE_TRANSACTION_LOG) {
+            Log.i(AbstractContactsProvider.TAG, "ContactsTransaction.finish  callerIsBatch=" +
+                    callerIsBatch, new RuntimeException("ContactsTransaction.finish"));
+        }
         if (!mBatch || callerIsBatch) {
             for (SQLiteDatabase db : mDatabasesForTransaction) {
+                if (AbstractContactsProvider.ENABLE_TRANSACTION_LOG) {
+                    Log.i(AbstractContactsProvider.TAG, "ContactsTransaction.finish: " +
+                            "endTransaction for " + getTagForDb(db));
+                }
                 // If an exception was thrown while yielding, it's possible that we no longer have
                 // a lock on this database, so we need to check before attempting to end its
                 // transaction.  Otherwise, we should always expect to be in a transaction (and will
diff --git a/src/com/android/providers/contacts/DataRowHandler.java b/src/com/android/providers/contacts/DataRowHandler.java
index b98f6c7..0366532 100644
--- a/src/com/android/providers/contacts/DataRowHandler.java
+++ b/src/com/android/providers/contacts/DataRowHandler.java
@@ -15,12 +15,6 @@
  */
 package com.android.providers.contacts;
 
-import com.android.providers.contacts.ContactsDatabaseHelper.DataColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.MimetypesColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.PresenceColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
-import com.android.providers.contacts.aggregation.ContactAggregator;
-
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
@@ -33,6 +27,12 @@
 import android.provider.ContactsContract.Data;
 import android.text.TextUtils;
 
+import com.android.providers.contacts.ContactsDatabaseHelper.DataColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.MimetypesColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.PresenceColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
+import com.android.providers.contacts.aggregation.ContactAggregator;
+
 /**
  * Handles inserts and update for a specific Data type.
  */
diff --git a/src/com/android/providers/contacts/DataRowHandlerForCommonDataKind.java b/src/com/android/providers/contacts/DataRowHandlerForCommonDataKind.java
index b717d31..0bb17c2 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForCommonDataKind.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForCommonDataKind.java
@@ -15,8 +15,6 @@
  */
 package com.android.providers.contacts;
 
-import com.android.providers.contacts.aggregation.ContactAggregator;
-
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
@@ -24,6 +22,8 @@
 import android.provider.ContactsContract.CommonDataKinds.BaseTypes;
 import android.text.TextUtils;
 
+import com.android.providers.contacts.aggregation.ContactAggregator;
+
 /**
  * Superclass for data row handlers that deal with types (e.g. Home, Work, Other) and
  * labels, which are custom types.
diff --git a/src/com/android/providers/contacts/DataRowHandlerForCustomMimetype.java b/src/com/android/providers/contacts/DataRowHandlerForCustomMimetype.java
index 0202fd6..502b835 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForCustomMimetype.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForCustomMimetype.java
@@ -15,10 +15,10 @@
  */
 package com.android.providers.contacts;
 
-import com.android.providers.contacts.aggregation.ContactAggregator;
-
 import android.content.Context;
 
+import com.android.providers.contacts.aggregation.ContactAggregator;
+
 public class DataRowHandlerForCustomMimetype extends DataRowHandler {
 
     public DataRowHandlerForCustomMimetype(Context context,
diff --git a/src/com/android/providers/contacts/DataRowHandlerForEmail.java b/src/com/android/providers/contacts/DataRowHandlerForEmail.java
index f1fa941..38cb2e1 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForEmail.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForEmail.java
@@ -15,15 +15,15 @@
  */
 package com.android.providers.contacts;
 
-import com.android.providers.contacts.SearchIndexManager.IndexBuilder;
-import com.android.providers.contacts.aggregation.ContactAggregator;
-
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.provider.ContactsContract.CommonDataKinds.Email;
 
+import com.android.providers.contacts.SearchIndexManager.IndexBuilder;
+import com.android.providers.contacts.aggregation.ContactAggregator;
+
 /**
  * Handler for email address data rows.
  */
diff --git a/src/com/android/providers/contacts/DataRowHandlerForGroupMembership.java b/src/com/android/providers/contacts/DataRowHandlerForGroupMembership.java
index 3a4b167..0d2427a 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForGroupMembership.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForGroupMembership.java
@@ -15,15 +15,6 @@
  */
 package com.android.providers.contacts;
 
-import com.android.providers.contacts.ContactsDatabaseHelper.Clauses;
-import com.android.providers.contacts.ContactsDatabaseHelper.DataColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.GroupsColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.Projections;
-import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
-import com.android.providers.contacts.ContactsProvider2.GroupIdCacheEntry;
-import com.android.providers.contacts.aggregation.ContactAggregator;
-
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
@@ -33,6 +24,15 @@
 import android.provider.ContactsContract.Groups;
 import android.provider.ContactsContract.RawContacts;
 
+import com.android.providers.contacts.ContactsDatabaseHelper.Clauses;
+import com.android.providers.contacts.ContactsDatabaseHelper.DataColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.GroupsColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.Projections;
+import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
+import com.android.providers.contacts.ContactsProvider2.GroupIdCacheEntry;
+import com.android.providers.contacts.aggregation.ContactAggregator;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 
diff --git a/src/com/android/providers/contacts/DataRowHandlerForIdentity.java b/src/com/android/providers/contacts/DataRowHandlerForIdentity.java
index 440e430..48ce5e4 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForIdentity.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForIdentity.java
@@ -15,14 +15,14 @@
  */
 package com.android.providers.contacts;
 
-import com.android.providers.contacts.aggregation.ContactAggregator;
-
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.provider.ContactsContract.CommonDataKinds.Identity;
 
+import com.android.providers.contacts.aggregation.ContactAggregator;
+
 /**
  * Handler for Identity data rows.
  */
diff --git a/src/com/android/providers/contacts/DataRowHandlerForIm.java b/src/com/android/providers/contacts/DataRowHandlerForIm.java
index 009bb89..faf10ad 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForIm.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForIm.java
@@ -15,13 +15,13 @@
  */
 package com.android.providers.contacts;
 
-import com.android.providers.contacts.SearchIndexManager.IndexBuilder;
-import com.android.providers.contacts.aggregation.ContactAggregator;
-
 import android.content.ContentValues;
 import android.content.Context;
 import android.provider.ContactsContract.CommonDataKinds.Im;
 
+import com.android.providers.contacts.SearchIndexManager.IndexBuilder;
+import com.android.providers.contacts.aggregation.ContactAggregator;
+
 /**
  * Handler for IM address data rows.
  */
diff --git a/src/com/android/providers/contacts/DataRowHandlerForNickname.java b/src/com/android/providers/contacts/DataRowHandlerForNickname.java
index 0fec6ee..95f24f5 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForNickname.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForNickname.java
@@ -15,9 +15,6 @@
  */
 package com.android.providers.contacts;
 
-import com.android.providers.contacts.SearchIndexManager.IndexBuilder;
-import com.android.providers.contacts.aggregation.ContactAggregator;
-
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
@@ -25,6 +22,9 @@
 import android.provider.ContactsContract.CommonDataKinds.Nickname;
 import android.text.TextUtils;
 
+import com.android.providers.contacts.SearchIndexManager.IndexBuilder;
+import com.android.providers.contacts.aggregation.ContactAggregator;
+
 /**
  * Handler for nickname data rows.
  */
diff --git a/src/com/android/providers/contacts/DataRowHandlerForNote.java b/src/com/android/providers/contacts/DataRowHandlerForNote.java
index 317af1a..ea73637 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForNote.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForNote.java
@@ -15,13 +15,13 @@
  */
 package com.android.providers.contacts;
 
-import com.android.providers.contacts.SearchIndexManager.IndexBuilder;
-import com.android.providers.contacts.aggregation.ContactAggregator;
-
 import android.content.ContentValues;
 import android.content.Context;
 import android.provider.ContactsContract.CommonDataKinds.Note;
 
+import com.android.providers.contacts.SearchIndexManager.IndexBuilder;
+import com.android.providers.contacts.aggregation.ContactAggregator;
+
 /**
  * Handler for note data rows.
  */
diff --git a/src/com/android/providers/contacts/DataRowHandlerForOrganization.java b/src/com/android/providers/contacts/DataRowHandlerForOrganization.java
index 7384ccb..44146e8 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForOrganization.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForOrganization.java
@@ -15,10 +15,6 @@
  */
 package com.android.providers.contacts;
 
-import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
-import com.android.providers.contacts.SearchIndexManager.IndexBuilder;
-import com.android.providers.contacts.aggregation.ContactAggregator;
-
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
@@ -27,6 +23,10 @@
 import android.provider.ContactsContract.CommonDataKinds.Organization;
 import android.provider.ContactsContract.Data;
 
+import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
+import com.android.providers.contacts.SearchIndexManager.IndexBuilder;
+import com.android.providers.contacts.aggregation.ContactAggregator;
+
 /**
  * Handler for organization data rows.
  */
diff --git a/src/com/android/providers/contacts/DataRowHandlerForPhoneNumber.java b/src/com/android/providers/contacts/DataRowHandlerForPhoneNumber.java
index 99313e9..da7d66c 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForPhoneNumber.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForPhoneNumber.java
@@ -15,11 +15,6 @@
  */
 package com.android.providers.contacts;
 
-import com.android.providers.contacts.ContactsDatabaseHelper.PhoneLookupColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
-import com.android.providers.contacts.SearchIndexManager.IndexBuilder;
-import com.android.providers.contacts.aggregation.ContactAggregator;
-
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
@@ -28,6 +23,11 @@
 import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
 
+import com.android.providers.contacts.ContactsDatabaseHelper.PhoneLookupColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
+import com.android.providers.contacts.SearchIndexManager.IndexBuilder;
+import com.android.providers.contacts.aggregation.ContactAggregator;
+
 /**
  * Handler for phone number data rows.
  */
diff --git a/src/com/android/providers/contacts/DataRowHandlerForPhoto.java b/src/com/android/providers/contacts/DataRowHandlerForPhoto.java
index 7560ed4..bfaa501 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForPhoto.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForPhoto.java
@@ -15,8 +15,6 @@
  */
 package com.android.providers.contacts;
 
-import com.android.providers.contacts.aggregation.ContactAggregator;
-
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
@@ -24,6 +22,8 @@
 import android.provider.ContactsContract.CommonDataKinds.Photo;
 import android.util.Log;
 
+import com.android.providers.contacts.aggregation.ContactAggregator;
+
 import java.io.IOException;
 
 /**
diff --git a/src/com/android/providers/contacts/DataRowHandlerForStructuredName.java b/src/com/android/providers/contacts/DataRowHandlerForStructuredName.java
index c84d2e8..01ee1ba 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForStructuredName.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForStructuredName.java
@@ -15,9 +15,6 @@
  */
 package com.android.providers.contacts;
 
-import com.android.providers.contacts.SearchIndexManager.IndexBuilder;
-import com.android.providers.contacts.aggregation.ContactAggregator;
-
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
@@ -26,6 +23,9 @@
 import android.provider.ContactsContract.FullNameStyle;
 import android.text.TextUtils;
 
+import com.android.providers.contacts.SearchIndexManager.IndexBuilder;
+import com.android.providers.contacts.aggregation.ContactAggregator;
+
 /**
  * Handler for email address data rows.
  */
diff --git a/src/com/android/providers/contacts/DataRowHandlerForStructuredPostal.java b/src/com/android/providers/contacts/DataRowHandlerForStructuredPostal.java
index 6898a43..26483ed 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForStructuredPostal.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForStructuredPostal.java
@@ -15,9 +15,6 @@
  */
 package com.android.providers.contacts;
 
-import com.android.providers.contacts.SearchIndexManager.IndexBuilder;
-import com.android.providers.contacts.aggregation.ContactAggregator;
-
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
@@ -25,6 +22,9 @@
 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
 import android.text.TextUtils;
 
+import com.android.providers.contacts.SearchIndexManager.IndexBuilder;
+import com.android.providers.contacts.aggregation.ContactAggregator;
+
 /**
  * Handler for postal address data rows.
  */
diff --git a/src/com/android/providers/contacts/DbModifierWithNotification.java b/src/com/android/providers/contacts/DbModifierWithNotification.java
index c13f4a8..fda8321 100644
--- a/src/com/android/providers/contacts/DbModifierWithNotification.java
+++ b/src/com/android/providers/contacts/DbModifierWithNotification.java
@@ -20,11 +20,6 @@
 import static android.Manifest.permission.ADD_VOICEMAIL;
 import static com.android.providers.contacts.Manifest.permission.READ_WRITE_ALL_VOICEMAIL;
 
-import com.android.common.io.MoreCloseables;
-import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
-import com.android.providers.contacts.util.DbQueryUtils;
-import com.google.android.collect.Lists;
-
 import android.content.ComponentName;
 import android.content.ContentUris;
 import android.content.ContentValues;
@@ -43,6 +38,11 @@
 import android.provider.VoicemailContract.Voicemails;
 import android.util.Log;
 
+import com.android.common.io.MoreCloseables;
+import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
+import com.android.providers.contacts.util.DbQueryUtils;
+import com.google.android.collect.Lists;
+
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashSet;
diff --git a/src/com/android/providers/contacts/DefaultCallLogInsertionHelper.java b/src/com/android/providers/contacts/DefaultCallLogInsertionHelper.java
index 6777e43..fcda8f1 100644
--- a/src/com/android/providers/contacts/DefaultCallLogInsertionHelper.java
+++ b/src/com/android/providers/contacts/DefaultCallLogInsertionHelper.java
@@ -16,16 +16,15 @@
 
 package com.android.providers.contacts;
 
+import android.content.ContentValues;
+import android.content.Context;
+import android.provider.CallLog.Calls;
+
 import com.android.i18n.phonenumbers.NumberParseException;
 import com.android.i18n.phonenumbers.PhoneNumberUtil;
 import com.android.i18n.phonenumbers.Phonenumber.PhoneNumber;
 import com.android.i18n.phonenumbers.geocoding.PhoneNumberOfflineGeocoder;
 
-import android.content.ContentValues;
-import android.content.Context;
-import android.provider.CallLog.Calls;
-import android.util.Log;
-
 import java.util.Locale;
 
 /**
diff --git a/src/com/android/providers/contacts/FastScrollingIndexCache.java b/src/com/android/providers/contacts/FastScrollingIndexCache.java
index c1c5602..f07a855 100644
--- a/src/com/android/providers/contacts/FastScrollingIndexCache.java
+++ b/src/com/android/providers/contacts/FastScrollingIndexCache.java
@@ -16,11 +16,9 @@
 
 package com.android.providers.contacts;
 
-import com.google.android.collect.Maps;
-import com.google.common.annotations.VisibleForTesting;
-
 import android.content.Context;
 import android.content.SharedPreferences;
+import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
 import android.preference.PreferenceManager;
@@ -28,6 +26,9 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.google.android.collect.Maps;
+import com.google.common.annotations.VisibleForTesting;
+
 import java.util.Map;
 import java.util.regex.Pattern;
 
diff --git a/src/com/android/providers/contacts/LegacyApiSupport.java b/src/com/android/providers/contacts/LegacyApiSupport.java
index 78cbc9d..9859d11 100644
--- a/src/com/android/providers/contacts/LegacyApiSupport.java
+++ b/src/com/android/providers/contacts/LegacyApiSupport.java
@@ -15,19 +15,6 @@
  */
 package com.android.providers.contacts;
 
-import com.android.providers.contacts.ContactsDatabaseHelper.AccountsColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.DataColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.ExtensionsColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.GroupsColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.MimetypesColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.NameLookupColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.NameLookupType;
-import com.android.providers.contacts.ContactsDatabaseHelper.PhoneLookupColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.PresenceColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.StatusUpdatesColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
-
 import android.accounts.Account;
 import android.app.SearchManager;
 import android.content.ContentUris;
@@ -65,6 +52,19 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.providers.contacts.ContactsDatabaseHelper.AccountsColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.DataColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.ExtensionsColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.GroupsColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.MimetypesColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.NameLookupColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.NameLookupType;
+import com.android.providers.contacts.ContactsDatabaseHelper.PhoneLookupColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.PresenceColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.StatusUpdatesColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
+
 import java.util.HashMap;
 import java.util.Locale;
 
diff --git a/src/com/android/providers/contacts/NameLookupBuilder.java b/src/com/android/providers/contacts/NameLookupBuilder.java
index 5ebbcd1..8375b88 100644
--- a/src/com/android/providers/contacts/NameLookupBuilder.java
+++ b/src/com/android/providers/contacts/NameLookupBuilder.java
@@ -16,11 +16,11 @@
 
 package com.android.providers.contacts;
 
+import android.provider.ContactsContract.FullNameStyle;
+
 import com.android.providers.contacts.ContactsDatabaseHelper.NameLookupType;
 import com.android.providers.contacts.SearchIndexManager.IndexBuilder;
 
-import android.provider.ContactsContract.FullNameStyle;
-
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.Iterator;
diff --git a/src/com/android/providers/contacts/NameNormalizer.java b/src/com/android/providers/contacts/NameNormalizer.java
index d91bd7c..c2b945e 100644
--- a/src/com/android/providers/contacts/NameNormalizer.java
+++ b/src/com/android/providers/contacts/NameNormalizer.java
@@ -17,10 +17,10 @@
 
 import com.android.providers.contacts.util.Hex;
 
-import java.util.Locale;
-import java.text.Collator;
 import java.text.CollationKey;
+import java.text.Collator;
 import java.text.RuleBasedCollator;
+import java.util.Locale;
 
 /**
  * Converts a name to a normalized form by removing all non-letter characters and normalizing
diff --git a/src/com/android/providers/contacts/NameSplitter.java b/src/com/android/providers/contacts/NameSplitter.java
index fd5b096..43743ee 100644
--- a/src/com/android/providers/contacts/NameSplitter.java
+++ b/src/com/android/providers/contacts/NameSplitter.java
@@ -15,14 +15,14 @@
  */
 package com.android.providers.contacts;
 
-import com.android.providers.contacts.util.NeededForTesting;
-
 import android.content.ContentValues;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
 import android.provider.ContactsContract.FullNameStyle;
 import android.provider.ContactsContract.PhoneticNameStyle;
-import android.provider.ContactsContract.CommonDataKinds.StructuredName;
 import android.text.TextUtils;
 
+import com.android.providers.contacts.util.NeededForTesting;
+
 import java.lang.Character.UnicodeBlock;
 import java.util.HashSet;
 import java.util.Locale;
diff --git a/src/com/android/providers/contacts/PhotoPriorityResolver.java b/src/com/android/providers/contacts/PhotoPriorityResolver.java
index c0dc4d9..150811c 100644
--- a/src/com/android/providers/contacts/PhotoPriorityResolver.java
+++ b/src/com/android/providers/contacts/PhotoPriorityResolver.java
@@ -16,22 +16,22 @@
 
 package com.android.providers.contacts;
 
-import com.android.internal.util.XmlUtils;
-import com.google.android.collect.Maps;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
 import android.accounts.AccountManager;
 import android.accounts.AuthenticatorDescription;
 import android.content.Context;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.ServiceInfo;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ServiceInfo;
 import android.content.res.XmlResourceParser;
 import android.util.Log;
 
+import com.android.internal.util.XmlUtils;
+import com.google.android.collect.Maps;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
 import java.io.IOException;
 import java.util.HashMap;
 
diff --git a/src/com/android/providers/contacts/PhotoProcessor.java b/src/com/android/providers/contacts/PhotoProcessor.java
index cf81ff3..1b8fc3f 100644
--- a/src/com/android/providers/contacts/PhotoProcessor.java
+++ b/src/com/android/providers/contacts/PhotoProcessor.java
@@ -15,13 +15,18 @@
  */
 package com.android.providers.contacts;
 
-import com.android.providers.contacts.util.MemoryUtils;
-
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
-import android.graphics.Matrix;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
 import android.os.SystemProperties;
 
+import com.android.providers.contacts.util.MemoryUtils;
+import com.google.common.annotations.VisibleForTesting;
+
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 
@@ -43,6 +48,12 @@
     /** Compression for thumbnails that also have a display photo */
     private static final int COMPRESSION_THUMBNAIL_LOW = 90;
 
+    private static final Paint WHITE_PAINT = new Paint();
+
+    static {
+        WHITE_PAINT.setColor(Color.WHITE);
+    }
+
     private static int sMaxThumbnailDim;
     private static int sMaxDisplayPhotoDim;
 
@@ -169,43 +180,65 @@
         if (mOriginal == null) {
             throw new IOException("Invalid image file");
         }
-        mDisplayPhoto = getScaledBitmap(mMaxDisplayPhotoDim);
-        mThumbnailPhoto = getScaledBitmap(mMaxThumbnailPhotoDim);
+        mDisplayPhoto = getNormalizedBitmap(mOriginal, mMaxDisplayPhotoDim, mForceCropToSquare);
+        mThumbnailPhoto = getNormalizedBitmap(mOriginal,mMaxThumbnailPhotoDim, mForceCropToSquare);
     }
 
     /**
      * Scales down the original bitmap to fit within the given maximum width and height.
      * If the bitmap already fits in those dimensions, the original bitmap will be
      * returned unmodified unless the photo processor is set up to crop it to a square.
+     *
+     * Also, if the image has transparency, conevrt it to white.
+     *
+     * @param original Original bitmap
      * @param maxDim Maximum width and height (in pixels) for the image.
+     * @param forceCropToSquare See {@link #PhotoProcessor(Bitmap, int, int, boolean)}
      * @return A bitmap that fits the maximum dimensions.
      */
     @SuppressWarnings({"SuspiciousNameCombination"})
-    private Bitmap getScaledBitmap(int maxDim) {
-        Bitmap scaledBitmap = mOriginal;
-        int width = mOriginal.getWidth();
-        int height = mOriginal.getHeight();
+    @VisibleForTesting
+    static Bitmap getNormalizedBitmap(Bitmap original, int maxDim, boolean forceCropToSquare) {
+        final boolean originalHasAlpha = original.hasAlpha();
+
+        // All cropXxx's are in the original coordinate.
+        int cropWidth = original.getWidth();
+        int cropHeight = original.getHeight();
         int cropLeft = 0;
         int cropTop = 0;
-        if (mForceCropToSquare && width != height) {
+        if (forceCropToSquare && cropWidth != cropHeight) {
             // Crop the image to the square at its center.
-            if (height > width) {
-                cropTop = (height - width) / 2;
-                height = width;
+            if (cropHeight > cropWidth) {
+                cropTop = (cropHeight - cropWidth) / 2;
+                cropHeight = cropWidth;
             } else {
-                cropLeft = (width - height) / 2;
-                width = height;
+                cropLeft = (cropWidth - cropHeight) / 2;
+                cropWidth = cropHeight;
             }
         }
-        float scaleFactor = ((float) maxDim) / Math.max(width, height);
-        if (scaleFactor < 1.0f || cropLeft != 0 || cropTop != 0) {
-            // Need to scale or crop the photo.
-            Matrix matrix = new Matrix();
-            if (scaleFactor < 1.0f) matrix.setScale(scaleFactor, scaleFactor);
-            scaledBitmap = Bitmap.createBitmap(
-                    mOriginal, cropLeft, cropTop, width, height, matrix, true);
+        // Calculate the scale factor.  We don't want to scale up, so the max scale is 1f.
+        final float scaleFactor = Math.min(1f, ((float) maxDim) / Math.max(cropWidth, cropHeight));
+
+        if (scaleFactor < 1.0f || cropLeft != 0 || cropTop != 0 || originalHasAlpha) {
+            final int newWidth = (int) (cropWidth * scaleFactor);
+            final int newHeight = (int) (cropHeight * scaleFactor);
+            final Bitmap scaledBitmap = Bitmap.createBitmap(newWidth, newHeight,
+                    Bitmap.Config.ARGB_8888);
+            final Canvas c = new Canvas(scaledBitmap);
+
+            if (originalHasAlpha) {
+                c.drawRect(0, 0, scaledBitmap.getWidth(), scaledBitmap.getHeight(), WHITE_PAINT);
+            }
+
+            final Rect src = new Rect(cropLeft, cropTop,
+                    cropLeft + cropWidth, cropTop + cropHeight);
+            final RectF dst = new RectF(0, 0, scaledBitmap.getWidth(), scaledBitmap.getHeight());
+
+            c.drawBitmap(original, src, dst, null);
+            return scaledBitmap;
+        } else {
+            return original;
         }
-        return scaledBitmap;
     }
 
     /**
diff --git a/src/com/android/providers/contacts/PhotoStore.java b/src/com/android/providers/contacts/PhotoStore.java
index e0b5fb4..e7be48c 100644
--- a/src/com/android/providers/contacts/PhotoStore.java
+++ b/src/com/android/providers/contacts/PhotoStore.java
@@ -15,17 +15,16 @@
  */
 package com.android.providers.contacts;
 
-import com.android.providers.contacts.ContactsDatabaseHelper.PhotoFilesColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
-
-import com.google.common.annotations.VisibleForTesting;
-
 import android.content.ContentValues;
 import android.database.sqlite.SQLiteDatabase;
 import android.graphics.Bitmap;
 import android.provider.ContactsContract.PhotoFiles;
 import android.util.Log;
 
+import com.android.providers.contacts.ContactsDatabaseHelper.PhotoFilesColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
+import com.google.common.annotations.VisibleForTesting;
+
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
diff --git a/src/com/android/providers/contacts/ProfileAwareUriMatcher.java b/src/com/android/providers/contacts/ProfileAwareUriMatcher.java
index de5cce1..051c60e 100644
--- a/src/com/android/providers/contacts/ProfileAwareUriMatcher.java
+++ b/src/com/android/providers/contacts/ProfileAwareUriMatcher.java
@@ -16,13 +16,13 @@
 
 package com.android.providers.contacts;
 
-import com.google.android.collect.Lists;
-import com.google.android.collect.Maps;
-
 import android.content.UriMatcher;
 import android.net.Uri;
 import android.provider.ContactsContract;
 
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+
 import java.util.List;
 import java.util.Map;
 import java.util.regex.Pattern;
diff --git a/src/com/android/providers/contacts/ProfileDatabaseHelper.java b/src/com/android/providers/contacts/ProfileDatabaseHelper.java
index 9b707a3..a23e521 100644
--- a/src/com/android/providers/contacts/ProfileDatabaseHelper.java
+++ b/src/com/android/providers/contacts/ProfileDatabaseHelper.java
@@ -16,13 +16,13 @@
 
 package com.android.providers.contacts;
 
-import com.android.providers.contacts.util.NeededForTesting;
-
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.sqlite.SQLiteDatabase;
 import android.provider.ContactsContract.Profile;
 
+import com.android.providers.contacts.util.NeededForTesting;
+
 /**
  * A separate version of the contacts database helper for storing the user's profile data.
  */
diff --git a/src/com/android/providers/contacts/ProfileProvider.java b/src/com/android/providers/contacts/ProfileProvider.java
index d97760d..ba10e8b 100644
--- a/src/com/android/providers/contacts/ProfileProvider.java
+++ b/src/com/android/providers/contacts/ProfileProvider.java
@@ -81,7 +81,6 @@
     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
             String sortOrder, CancellationSignal cancellationSignal) {
         enforceReadPermission(uri);
-        mDelegate.substituteDb(getDatabaseHelper().getReadableDatabase());
         return mDelegate.queryLocal(uri, projection, selection, selectionArgs, sortOrder, -1,
                 cancellationSignal);
     }
@@ -112,10 +111,8 @@
     public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException {
         if (mode != null && mode.contains("w")) {
             enforceWritePermission();
-            mDelegate.substituteDb(getDatabaseHelper().getWritableDatabase());
         } else {
             enforceReadPermission(uri);
-            mDelegate.substituteDb(getDatabaseHelper().getReadableDatabase());
         }
         return mDelegate.openAssetFileLocal(uri, mode);
     }
@@ -124,7 +121,6 @@
         ContactsTransaction transaction = getCurrentTransaction();
         SQLiteDatabase db = getDatabaseHelper().getWritableDatabase();
         transaction.startTransactionForDb(db, ContactsProvider2.PROFILE_DB_TAG, this);
-        mDelegate.substituteDb(db);
     }
 
     @Override
@@ -142,20 +138,17 @@
 
     @Override
     public void onBegin() {
-        mDelegate.switchToProfileMode();
-        mDelegate.onBegin();
+        mDelegate.onBeginTransactionInternal(true);
     }
 
     @Override
     public void onCommit() {
-        mDelegate.switchToProfileMode();
-        mDelegate.onCommit();
+        mDelegate.onCommitTransactionInternal(true);
     }
 
     @Override
     public void onRollback() {
-        mDelegate.switchToProfileMode();
-        mDelegate.onRollback();
+        mDelegate.onRollbackTransactionInternal(true);
     }
 
     @Override
@@ -167,4 +160,10 @@
     public String getType(Uri uri) {
         return mDelegate.getType(uri);
     }
+
+    /** Use only for debug logging */
+    @Override
+    public String toString() {
+        return "ProfileProvider";
+    }
 }
diff --git a/src/com/android/providers/contacts/SearchIndexManager.java b/src/com/android/providers/contacts/SearchIndexManager.java
index bd4e1cc..361b5d8 100644
--- a/src/com/android/providers/contacts/SearchIndexManager.java
+++ b/src/com/android/providers/contacts/SearchIndexManager.java
@@ -15,14 +15,6 @@
  */
 package com.android.providers.contacts;
 
-import com.android.providers.contacts.ContactsDatabaseHelper.DataColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.MimetypesColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.SearchIndexColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
-import com.google.android.collect.Lists;
-import com.google.common.annotations.VisibleForTesting;
-
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
@@ -37,6 +29,14 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.providers.contacts.ContactsDatabaseHelper.DataColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.MimetypesColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.SearchIndexColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
+import com.google.android.collect.Lists;
+import com.google.common.annotations.VisibleForTesting;
+
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
diff --git a/src/com/android/providers/contacts/VoicemailCleanupService.java b/src/com/android/providers/contacts/VoicemailCleanupService.java
index 39f5be5..4ad1406 100644
--- a/src/com/android/providers/contacts/VoicemailCleanupService.java
+++ b/src/com/android/providers/contacts/VoicemailCleanupService.java
@@ -16,8 +16,6 @@
 
 package com.android.providers.contacts;
 
-import com.google.common.annotations.VisibleForTesting;
-
 import android.app.IntentService;
 import android.content.ContentResolver;
 import android.content.Intent;
@@ -25,6 +23,8 @@
 import android.provider.VoicemailContract.Voicemails;
 import android.util.Log;
 
+import com.google.common.annotations.VisibleForTesting;
+
 /**
  * A service that cleans up voicemail related data for packages that are uninstalled.
  */
diff --git a/src/com/android/providers/contacts/VoicemailContentProvider.java b/src/com/android/providers/contacts/VoicemailContentProvider.java
index 79d8f92..b2f6b1e 100644
--- a/src/com/android/providers/contacts/VoicemailContentProvider.java
+++ b/src/com/android/providers/contacts/VoicemailContentProvider.java
@@ -19,11 +19,6 @@
 import static com.android.providers.contacts.util.DbQueryUtils.concatenateClauses;
 import static com.android.providers.contacts.util.DbQueryUtils.getEqualityClause;
 
-import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
-import com.android.providers.contacts.util.SelectionBuilder;
-import com.android.providers.contacts.util.TypedUriMatcherImpl;
-import com.google.common.annotations.VisibleForTesting;
-
 import android.content.ContentProvider;
 import android.content.ContentValues;
 import android.content.Context;
@@ -38,6 +33,11 @@
 import android.provider.VoicemailContract.Voicemails;
 import android.util.Log;
 
+import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
+import com.android.providers.contacts.util.SelectionBuilder;
+import com.android.providers.contacts.util.TypedUriMatcherImpl;
+import com.google.common.annotations.VisibleForTesting;
+
 import java.io.FileNotFoundException;
 import java.util.List;
 
diff --git a/src/com/android/providers/contacts/VoicemailContentTable.java b/src/com/android/providers/contacts/VoicemailContentTable.java
index 3b72653..3f00b26 100644
--- a/src/com/android/providers/contacts/VoicemailContentTable.java
+++ b/src/com/android/providers/contacts/VoicemailContentTable.java
@@ -19,10 +19,6 @@
 import static com.android.providers.contacts.util.DbQueryUtils.concatenateClauses;
 import static com.android.providers.contacts.util.DbQueryUtils.getEqualityClause;
 
-import com.android.common.content.ProjectionMap;
-import com.android.providers.contacts.VoicemailContentProvider.UriData;
-import com.android.providers.contacts.util.CloseUtils;
-
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
@@ -38,6 +34,10 @@
 import android.provider.VoicemailContract.Voicemails;
 import android.util.Log;
 
+import com.android.common.content.ProjectionMap;
+import com.android.providers.contacts.VoicemailContentProvider.UriData;
+import com.android.providers.contacts.util.CloseUtils;
+
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
diff --git a/src/com/android/providers/contacts/VoicemailStatusTable.java b/src/com/android/providers/contacts/VoicemailStatusTable.java
index a0a61ba..2c1861b 100644
--- a/src/com/android/providers/contacts/VoicemailStatusTable.java
+++ b/src/com/android/providers/contacts/VoicemailStatusTable.java
@@ -18,9 +18,6 @@
 
 import static com.android.providers.contacts.util.DbQueryUtils.concatenateClauses;
 
-import com.android.common.content.ProjectionMap;
-import com.android.providers.contacts.VoicemailContentProvider.UriData;
-
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
@@ -32,6 +29,9 @@
 import android.os.ParcelFileDescriptor;
 import android.provider.VoicemailContract.Status;
 
+import com.android.common.content.ProjectionMap;
+import com.android.providers.contacts.VoicemailContentProvider.UriData;
+
 /**
  * Implementation of {@link VoicemailTable.Delegate} for the voicemail status table.
  */
diff --git a/src/com/android/providers/contacts/VoicemailTable.java b/src/com/android/providers/contacts/VoicemailTable.java
index db35c98..9e6c431 100644
--- a/src/com/android/providers/contacts/VoicemailTable.java
+++ b/src/com/android/providers/contacts/VoicemailTable.java
@@ -16,13 +16,13 @@
 
 package com.android.providers.contacts;
 
-import com.android.providers.contacts.VoicemailContentProvider.UriData;
-
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.ParcelFileDescriptor;
 
+import com.android.providers.contacts.VoicemailContentProvider.UriData;
+
 import java.io.FileNotFoundException;
 
 /**
diff --git a/src/com/android/providers/contacts/aggregation/ContactAggregator.java b/src/com/android/providers/contacts/aggregation/ContactAggregator.java
index d3931e6..baae2e5 100644
--- a/src/com/android/providers/contacts/aggregation/ContactAggregator.java
+++ b/src/com/android/providers/contacts/aggregation/ContactAggregator.java
@@ -16,6 +16,29 @@
 
 package com.android.providers.contacts.aggregation;
 
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.database.sqlite.SQLiteStatement;
+import android.net.Uri;
+import android.provider.ContactsContract.AggregationExceptions;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Identity;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Contacts.AggregationSuggestions;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.DisplayNameSources;
+import android.provider.ContactsContract.FullNameStyle;
+import android.provider.ContactsContract.PhotoFiles;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.StatusUpdates;
+import android.text.TextUtils;
+import android.util.EventLog;
+import android.util.Log;
+
 import com.android.providers.contacts.ContactLookupKey;
 import com.android.providers.contacts.ContactsDatabaseHelper;
 import com.android.providers.contacts.ContactsDatabaseHelper.AccountsColumns;
@@ -42,29 +65,6 @@
 import com.android.providers.contacts.aggregation.util.ContactMatcher.MatchScore;
 import com.google.android.collect.Maps;
 
-import android.database.Cursor;
-import android.database.DatabaseUtils;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteQueryBuilder;
-import android.database.sqlite.SQLiteStatement;
-import android.net.Uri;
-import android.provider.ContactsContract.AggregationExceptions;
-import android.provider.ContactsContract.CommonDataKinds.Email;
-import android.provider.ContactsContract.CommonDataKinds.Identity;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.Photo;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Contacts.AggregationSuggestions;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.DisplayNameSources;
-import android.provider.ContactsContract.FullNameStyle;
-import android.provider.ContactsContract.PhotoFiles;
-import android.provider.ContactsContract.RawContacts;
-import android.provider.ContactsContract.StatusUpdates;
-import android.text.TextUtils;
-import android.util.EventLog;
-import android.util.Log;
-
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
diff --git a/src/com/android/providers/contacts/aggregation/ProfileAggregator.java b/src/com/android/providers/contacts/aggregation/ProfileAggregator.java
index fedf5fe..276a05f 100644
--- a/src/com/android/providers/contacts/aggregation/ProfileAggregator.java
+++ b/src/com/android/providers/contacts/aggregation/ProfileAggregator.java
@@ -23,11 +23,11 @@
 import com.android.providers.contacts.ContactLookupKey;
 import com.android.providers.contacts.ContactsDatabaseHelper;
 import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
-import com.android.providers.contacts.aggregation.util.CommonNicknameCache;
 import com.android.providers.contacts.ContactsProvider2;
 import com.android.providers.contacts.NameSplitter;
 import com.android.providers.contacts.PhotoPriorityResolver;
 import com.android.providers.contacts.TransactionContext;
+import com.android.providers.contacts.aggregation.util.CommonNicknameCache;
 
 /**
  * A version of the ContactAggregator for use against the profile database.
diff --git a/src/com/android/providers/contacts/aggregation/util/CommonNicknameCache.java b/src/com/android/providers/contacts/aggregation/util/CommonNicknameCache.java
index d6b799f..9643d81 100644
--- a/src/com/android/providers/contacts/aggregation/util/CommonNicknameCache.java
+++ b/src/com/android/providers/contacts/aggregation/util/CommonNicknameCache.java
@@ -16,13 +16,13 @@
 
 package com.android.providers.contacts.aggregation.util;
 
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+
 import com.android.providers.contacts.ContactsDatabaseHelper.NicknameLookupColumns;
 import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
 import com.google.android.collect.Maps;
 
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-
 import java.lang.ref.SoftReference;
 import java.util.BitSet;
 import java.util.HashMap;
diff --git a/tests/res/drawable-nodpi/transparent_10x10.png b/tests/res/drawable-nodpi/transparent_10x10.png
new file mode 100644
index 0000000..d11c2cd
--- /dev/null
+++ b/tests/res/drawable-nodpi/transparent_10x10.png
Binary files differ
diff --git a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
index 1154186..3759196 100644
--- a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
@@ -18,11 +18,6 @@
 
 import static com.android.providers.contacts.ContactsActor.PACKAGE_GREY;
 
-import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
-import com.android.providers.contacts.util.Hex;
-import com.android.providers.contacts.util.MockClock;
-import com.google.android.collect.Sets;
-
 import android.accounts.Account;
 import android.content.ContentProvider;
 import android.content.ContentResolver;
@@ -62,6 +57,11 @@
 import android.test.mock.MockContentResolver;
 import android.util.Log;
 
+import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
+import com.android.providers.contacts.util.Hex;
+import com.android.providers.contacts.util.MockClock;
+import com.google.android.collect.Sets;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.BitSet;
diff --git a/tests/src/com/android/providers/contacts/CallerInfoIntegrationTest.java b/tests/src/com/android/providers/contacts/CallerInfoIntegrationTest.java
index 0ce9bca..4c0d2df 100644
--- a/tests/src/com/android/providers/contacts/CallerInfoIntegrationTest.java
+++ b/tests/src/com/android/providers/contacts/CallerInfoIntegrationTest.java
@@ -16,14 +16,14 @@
 
 package com.android.providers.contacts;
 
-import com.android.internal.telephony.CallerInfo;
-
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.net.Uri;
 import android.provider.ContactsContract.RawContacts;
 import android.test.suitebuilder.annotation.MediumTest;
 
+import com.android.internal.telephony.CallerInfo;
+
 /**
  * Integration test for {@link CallerInfo} and {@link ContactsProvider2}.
  *
diff --git a/tests/src/com/android/providers/contacts/ContactDirectoryManagerTest.java b/tests/src/com/android/providers/contacts/ContactDirectoryManagerTest.java
index f1ff776..96cbb9b 100644
--- a/tests/src/com/android/providers/contacts/ContactDirectoryManagerTest.java
+++ b/tests/src/com/android/providers/contacts/ContactDirectoryManagerTest.java
@@ -16,9 +16,6 @@
 
 package com.android.providers.contacts;
 
-import com.android.providers.contacts.ContactsDatabaseHelper.AggregationExceptionColumns;
-import com.google.android.collect.Lists;
-
 import android.accounts.Account;
 import android.content.ContentValues;
 import android.content.Context;
@@ -39,6 +36,9 @@
 import android.test.suitebuilder.annotation.MediumTest;
 import android.util.Log;
 
+import com.android.providers.contacts.ContactsDatabaseHelper.AggregationExceptionColumns;
+import com.google.android.collect.Lists;
+
 /**
  * Unit tests for {@link ContactDirectoryManager}. Run the test like this:
  *
diff --git a/tests/src/com/android/providers/contacts/ContactLookupKeyTest.java b/tests/src/com/android/providers/contacts/ContactLookupKeyTest.java
index 739b2cb..08f3a07 100644
--- a/tests/src/com/android/providers/contacts/ContactLookupKeyTest.java
+++ b/tests/src/com/android/providers/contacts/ContactLookupKeyTest.java
@@ -16,8 +16,6 @@
 
 package com.android.providers.contacts;
 
-import com.android.providers.contacts.ContactLookupKey.LookupKeySegment;
-
 import android.content.ContentUris;
 import android.net.Uri;
 import android.provider.ContactsContract.AggregationExceptions;
@@ -25,6 +23,8 @@
 import android.provider.ContactsContract.RawContacts;
 import android.test.suitebuilder.annotation.MediumTest;
 
+import com.android.providers.contacts.ContactLookupKey.LookupKeySegment;
+
 import java.util.ArrayList;
 
 /**
diff --git a/tests/src/com/android/providers/contacts/ContactsActor.java b/tests/src/com/android/providers/contacts/ContactsActor.java
index 038eb97..e75c52e 100644
--- a/tests/src/com/android/providers/contacts/ContactsActor.java
+++ b/tests/src/com/android/providers/contacts/ContactsActor.java
@@ -16,9 +16,6 @@
 
 package com.android.providers.contacts;
 
-import com.android.providers.contacts.util.MockSharedPreferences;
-import com.google.android.collect.Sets;
-
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.accounts.AccountManagerCallback;
@@ -59,6 +56,9 @@
 import android.test.mock.MockContentResolver;
 import android.test.mock.MockContext;
 
+import com.android.providers.contacts.util.MockSharedPreferences;
+import com.google.android.collect.Sets;
+
 import java.io.File;
 import java.io.IOException;
 import java.util.Arrays;
diff --git a/tests/src/com/android/providers/contacts/ContactsDatabaseHelperTest.java b/tests/src/com/android/providers/contacts/ContactsDatabaseHelperTest.java
index a9d8a36..faddeea 100644
--- a/tests/src/com/android/providers/contacts/ContactsDatabaseHelperTest.java
+++ b/tests/src/com/android/providers/contacts/ContactsDatabaseHelperTest.java
@@ -16,11 +16,11 @@
 
 package com.android.providers.contacts;
 
+import android.test.suitebuilder.annotation.SmallTest;
+
 import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
 import com.google.android.collect.Sets;
 
-import android.test.suitebuilder.annotation.SmallTest;
-
 import java.util.Set;
 
 @SmallTest
diff --git a/tests/src/com/android/providers/contacts/ContactsMockPackageManager.java b/tests/src/com/android/providers/contacts/ContactsMockPackageManager.java
index 69cd9fa..694f0f3 100644
--- a/tests/src/com/android/providers/contacts/ContactsMockPackageManager.java
+++ b/tests/src/com/android/providers/contacts/ContactsMockPackageManager.java
@@ -15,8 +15,6 @@
  */
 package com.android.providers.contacts;
 
-import com.google.android.collect.Lists;
-
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
@@ -26,6 +24,8 @@
 import android.os.Binder;
 import android.test.mock.MockPackageManager;
 
+import com.google.android.collect.Lists;
+
 import java.util.HashMap;
 import java.util.List;
 
diff --git a/tests/src/com/android/providers/contacts/ContactsMockResources.java b/tests/src/com/android/providers/contacts/ContactsMockResources.java
index 248d6da..6d98665 100644
--- a/tests/src/com/android/providers/contacts/ContactsMockResources.java
+++ b/tests/src/com/android/providers/contacts/ContactsMockResources.java
@@ -16,10 +16,10 @@
 
 package com.android.providers.contacts;
 
-import com.google.android.collect.Maps;
-
 import android.test.mock.MockResources;
 
+import com.google.android.collect.Maps;
+
 import java.util.Map;
 
 final class ContactsMockResources extends MockResources {
diff --git a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
index 5706925..80e194d 100644
--- a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
@@ -18,16 +18,6 @@
 
 import static com.android.providers.contacts.TestUtils.cv;
 
-import com.android.internal.util.ArrayUtils;
-import com.android.providers.contacts.ContactsDatabaseHelper.AggregationExceptionColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.DataUsageStatColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.DbProperties;
-import com.android.providers.contacts.ContactsDatabaseHelper.PresenceColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
-import com.android.providers.contacts.tests.R;
-import com.google.android.collect.Lists;
-import com.google.android.collect.Sets;
-
 import android.accounts.Account;
 import android.content.ContentProviderOperation;
 import android.content.ContentProviderResult;
@@ -76,6 +66,16 @@
 import android.test.suitebuilder.annotation.LargeTest;
 import android.text.TextUtils;
 
+import com.android.internal.util.ArrayUtils;
+import com.android.providers.contacts.ContactsDatabaseHelper.AggregationExceptionColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.DataUsageStatColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.DbProperties;
+import com.android.providers.contacts.ContactsDatabaseHelper.PresenceColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
+import com.android.providers.contacts.tests.R;
+import com.google.android.collect.Lists;
+import com.google.android.collect.Sets;
+
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.OutputStream;
@@ -5756,7 +5756,7 @@
         PhotoStore profilePhotoStore = provider.getProfilePhotoStore();
 
         // Trigger an initial cleanup so another one won't happen while we're running this test.
-        provider.switchToProfileMode();
+        provider.switchToProfileModeForTest();
         provider.cleanupPhotoStore();
 
         // Create the profile contact and add a photo.
@@ -5787,7 +5787,7 @@
         profilePhotoStore.remove(streamItemPhotoFileId);
 
         // Manually trigger another cleanup in the provider.
-        provider.switchToProfileMode();
+        provider.switchToProfileModeForTest();
         provider.cleanupPhotoStore();
 
         // The following things should have happened.
diff --git a/tests/src/com/android/providers/contacts/ContactsProvider2TransactionTest.java b/tests/src/com/android/providers/contacts/ContactsProvider2TransactionTest.java
new file mode 100644
index 0000000..6a82bf9
--- /dev/null
+++ b/tests/src/com/android/providers/contacts/ContactsProvider2TransactionTest.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2012 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 static com.android.providers.contacts.TestUtils.cv;
+
+import com.google.android.collect.Lists;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentValues;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.Profile;
+import android.provider.ContactsContract.RawContacts;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+/**
+ * Tests to make sure we're handling DB transactions properly in regard to two databases,
+ * the profile db and the contacts db.
+ */
+@LargeTest
+public class ContactsProvider2TransactionTest extends BaseContactsProvider2Test {
+    private SynchronousContactsProvider2 mProvider;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mProvider = (SynchronousContactsProvider2) getProvider();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+
+        mProvider = null;
+    }
+
+    /**
+     * Make sure we start/finish transactions on the right databases for insert.
+     */
+    public void testTransactionCallback_insert() {
+
+        final ContentValues values = cv(RawContacts.LAST_TIME_CONTACTED, 12345);
+
+        // Insert a raw contact.
+        mProvider.resetTrasactionCallbackCalledFlags();
+        mResolver.insert(RawContacts.CONTENT_URI, values);
+
+        // Make sure we only COMMIT on the contacts DB, but there was no transaction on the
+        // profile db.
+        mProvider.assertCommitTransactionCalledForContactMode();
+        mProvider.assertNoTransactionsForProfileMode();
+
+
+        // Insert a profile raw contact.
+        mProvider.resetTrasactionCallbackCalledFlags();
+        mResolver.insert(Profile.CONTENT_RAW_CONTACTS_URI, values);
+
+        // Even though we only touched the profile DB, we also start and finish a transaction
+        // on the contacts db.  AbstractContactsProvider does that to avoid deadlocks.
+        mProvider.assertCommitTransactionCalledForContactMode();
+        mProvider.assertCommitTransactionCalledForProfileMode();
+    }
+
+    /**
+     * Make sure we start/finish transactions on the right databases for update.
+     */
+    public void testTransactionCallback_update() {
+
+        final ContentValues values = cv(RawContacts.LAST_TIME_CONTACTED, 12345);
+
+        // Make sure to create a raw contact and a profile raw contact.
+        mResolver.insert(RawContacts.CONTENT_URI, values);
+        mResolver.insert(Profile.CONTENT_RAW_CONTACTS_URI, values);
+
+        values.clear();
+        values.put(RawContacts.LAST_TIME_CONTACTED, 99999);
+
+        // Update all raw contacts.
+        mProvider.resetTrasactionCallbackCalledFlags();
+        assertTrue(mResolver.update(RawContacts.CONTENT_URI, values, null, null) > 0);
+
+        // Make sure we only COMMIT on the contacts DB, but there was no transaction on the
+        // profile db.
+        mProvider.assertCommitTransactionCalledForContactMode();
+        mProvider.assertNoTransactionsForProfileMode();
+
+
+        // Update all profile raw contacts.
+        mProvider.resetTrasactionCallbackCalledFlags();
+        assertTrue(mResolver.update(Profile.CONTENT_RAW_CONTACTS_URI, values, null, null) > 0);
+
+        // Even though we only touched the profile DB, we also start and finish a transaction
+        // on the contacts db.  AbstractContactsProvider does that to avoid deadlocks.
+        mProvider.assertCommitTransactionCalledForContactMode();
+        mProvider.assertCommitTransactionCalledForProfileMode();
+    }
+
+    /**
+     * Make sure we start/finish transactions on the right databases for delete.
+     */
+    public void testTransactionCallback_delete() {
+
+        final ContentValues values = cv(RawContacts.LAST_TIME_CONTACTED, 12345);
+
+        // Make sure to create a raw contact and a profile raw contact.
+        mResolver.insert(RawContacts.CONTENT_URI, values);
+        mResolver.insert(Profile.CONTENT_RAW_CONTACTS_URI, values);
+
+        // Delete all raw contacts.
+        mProvider.resetTrasactionCallbackCalledFlags();
+        assertTrue(mResolver.delete(RawContacts.CONTENT_URI, null, null) > 0);
+
+        // Make sure we only COMMIT on the contacts DB, but there was no transaction on the
+        // profile db.
+        mProvider.assertCommitTransactionCalledForContactMode();
+        mProvider.assertNoTransactionsForProfileMode();
+
+        // Delete all profile raw contact.
+        mProvider.resetTrasactionCallbackCalledFlags();
+        assertTrue(mResolver.delete(Profile.CONTENT_RAW_CONTACTS_URI, null, null) > 0);
+
+        // Even though we only touched the profile DB, we also start and finish a transaction
+        // on the contacts db.  AbstractContactsProvider does that to avoid deadlocks.
+        mProvider.assertCommitTransactionCalledForContactMode();
+        mProvider.assertCommitTransactionCalledForProfileMode();
+    }
+    /**
+     * Make sure we start/finish transactions on the right databases for bulk insert.
+     */
+    public void testTransactionCallback_bulkInsert() {
+
+        final ContentValues values = cv(RawContacts.LAST_TIME_CONTACTED, 12345);
+
+        // Insert a raw contact.
+        mProvider.resetTrasactionCallbackCalledFlags();
+        mResolver.bulkInsert(RawContacts.CONTENT_URI, new ContentValues[] {values});
+
+        // Make sure we only COMMIT on the contacts DB, but there was no transaction on the
+        // profile db.
+        mProvider.assertCommitTransactionCalledForContactMode();
+        mProvider.assertNoTransactionsForProfileMode();
+
+
+        // Insert a profile raw contact.
+        mProvider.resetTrasactionCallbackCalledFlags();
+        mResolver.bulkInsert(Profile.CONTENT_RAW_CONTACTS_URI, new ContentValues[] {values});
+
+        // Even though we only touched the profile DB, we also start and finish a transaction
+        // on the contacts db.  AbstractContactsProvider does that to avoid deadlocks.
+        mProvider.assertCommitTransactionCalledForContactMode();
+        mProvider.assertCommitTransactionCalledForProfileMode();
+    }
+
+    /**
+     * Add an operation to create a raw contact.
+     */
+    private static void addInsertContactOperations(ArrayList<ContentProviderOperation> ops) {
+        ContentProviderOperation.Builder b;
+        b = ContentProviderOperation.newInsert(RawContacts.CONTENT_URI);
+        b.withValue(RawContacts.STARRED, 1);
+        b.withValue(RawContacts.TIMES_CONTACTED, 200001);
+        ops.add(b.build());
+
+        b = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+        b.withValueBackReference(Data.RAW_CONTACT_ID, ops.size() - 1);
+        b.withValue(StructuredName.DISPLAY_NAME, "Regular Contact");
+        b.withValue(StructuredName.GIVEN_NAME, "Regular");
+        b.withValue(StructuredName.FAMILY_NAME, "Contact");
+        b.withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
+        ops.add(b.build());
+    }
+
+    /**
+     * Check for a contact created that'll be created for {@link #addInsertContactOperations}.
+     */
+    private void checkStoredContact() {
+        assertStoredValues(Contacts.CONTENT_URI, cv(
+                Contacts.DISPLAY_NAME, "Regular Contact",
+                RawContacts.TIMES_CONTACTED, 200001
+                ));
+    }
+
+    /**
+     * Add an operation to create a profile raw contact.
+     */
+    private static void addInsertProfileOperations(ArrayList<ContentProviderOperation> ops) {
+        ContentProviderOperation.Builder b;
+        b = ContentProviderOperation.newInsert(Profile.CONTENT_RAW_CONTACTS_URI);
+        b.withValue(RawContacts.STARRED, 1);
+        b.withValue(RawContacts.TIMES_CONTACTED, 100001);
+        ops.add(b.build());
+
+        b = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+        b.withValueBackReference(Data.RAW_CONTACT_ID, ops.size() - 1);
+        b.withValue(StructuredName.DISPLAY_NAME, "Profile Contact");
+        b.withValue(StructuredName.GIVEN_NAME, "Profile");
+        b.withValue(StructuredName.FAMILY_NAME, "Contact");
+        b.withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
+        ops.add(b.build());
+    }
+
+    /**
+     * Check for a profile contact created that'll be created for
+     * {@link #addInsertProfileOperations}.
+     */
+    private void checkStoredProfile() {
+        assertStoredValues(Profile.CONTENT_URI, cv(
+                Contacts.DISPLAY_NAME, "Profile Contact",
+                RawContacts.TIMES_CONTACTED, 100001
+                ));
+    }
+
+    public void testTransactionCallback_contactBatch() throws Exception {
+        final ArrayList<ContentProviderOperation> ops = Lists.newArrayList();
+
+        addInsertContactOperations(ops);
+
+        mProvider.resetTrasactionCallbackCalledFlags();
+
+        // Execute the operations.
+        mResolver.applyBatch(ContactsContract.AUTHORITY, ops);
+
+        // Check the result
+        mProvider.assertCommitTransactionCalledForContactMode();
+        mProvider.assertNoTransactionsForProfileMode();
+
+        checkStoredContact();
+    }
+
+    public void testTransactionCallback_profileBatch() throws Exception {
+        final ArrayList<ContentProviderOperation> ops = Lists.newArrayList();
+
+        addInsertProfileOperations(ops);
+
+        mProvider.resetTrasactionCallbackCalledFlags();
+
+        // Execute the operations.
+        mResolver.applyBatch(ContactsContract.AUTHORITY, ops);
+
+        // Check the result
+        mProvider.assertCommitTransactionCalledForContactMode();
+        mProvider.assertCommitTransactionCalledForProfileMode();
+
+        checkStoredProfile();
+    }
+
+    public void testTransactionCallback_mixedBatch() throws Exception {
+        final ArrayList<ContentProviderOperation> ops = Lists.newArrayList();
+
+        // Create a raw contact and a profile raw contact in a single batch.
+
+        addInsertContactOperations(ops);
+        addInsertProfileOperations(ops);
+
+        mProvider.resetTrasactionCallbackCalledFlags();
+
+        // Execute the operations.
+        mResolver.applyBatch(ContactsContract.AUTHORITY, ops);
+
+        // Check the result
+        mProvider.assertCommitTransactionCalledForContactMode();
+        mProvider.assertCommitTransactionCalledForProfileMode();
+
+        checkStoredProfile();
+        checkStoredContact();
+    }
+
+    public void testTransactionCallback_mixedBatchReversed() throws Exception {
+        final ArrayList<ContentProviderOperation> ops = Lists.newArrayList();
+
+        // Create a profile raw contact and a raw contact in a single batch.
+
+        addInsertProfileOperations(ops);
+        addInsertContactOperations(ops);
+
+        mProvider.resetTrasactionCallbackCalledFlags();
+
+        // Execute the operations.
+        mResolver.applyBatch(ContactsContract.AUTHORITY, ops);
+
+        // Check the result
+        mProvider.assertCommitTransactionCalledForContactMode();
+        mProvider.assertCommitTransactionCalledForProfileMode();
+
+        checkStoredProfile();
+        checkStoredContact();
+    }
+}
diff --git a/tests/src/com/android/providers/contacts/DirectoryTest.java b/tests/src/com/android/providers/contacts/DirectoryTest.java
index e9592c1..c62824b 100644
--- a/tests/src/com/android/providers/contacts/DirectoryTest.java
+++ b/tests/src/com/android/providers/contacts/DirectoryTest.java
@@ -26,7 +26,7 @@
 import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.Directory;
-import android.test.suitebuilder.annotation.MediumTest;;
+import android.test.suitebuilder.annotation.MediumTest;
 
 
 /**
diff --git a/tests/src/com/android/providers/contacts/EvenMoreAsserts.java b/tests/src/com/android/providers/contacts/EvenMoreAsserts.java
index c73128a..1fc15f2 100644
--- a/tests/src/com/android/providers/contacts/EvenMoreAsserts.java
+++ b/tests/src/com/android/providers/contacts/EvenMoreAsserts.java
@@ -16,19 +16,19 @@
 
 package com.android.providers.contacts;
 
-import com.google.android.collect.Sets;
-
 import android.content.Context;
 import android.graphics.BitmapFactory;
 
+import com.google.android.collect.Sets;
+
+import junit.framework.Assert;
+
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.Arrays;
 import java.util.Set;
 
-import junit.framework.Assert;
-
 /**
  * Contains additional assertion methods not found in Junit or MoreAsserts.
  */
diff --git a/tests/src/com/android/providers/contacts/FastScrollingIndexCacheTest.java b/tests/src/com/android/providers/contacts/FastScrollingIndexCacheTest.java
index 7ca2a87..281834f 100644
--- a/tests/src/com/android/providers/contacts/FastScrollingIndexCacheTest.java
+++ b/tests/src/com/android/providers/contacts/FastScrollingIndexCacheTest.java
@@ -16,8 +16,6 @@
 
 package com.android.providers.contacts;
 
-import com.android.providers.contacts.util.MockSharedPreferences;
-
 import android.net.Uri;
 import android.os.Bundle;
 import android.provider.ContactsContract.ContactCounts;
@@ -27,6 +25,8 @@
 import android.test.MoreAsserts;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.providers.contacts.util.MockSharedPreferences;
+
 @SmallTest
 public class FastScrollingIndexCacheTest extends AndroidTestCase {
     private MockSharedPreferences mPrefs;
diff --git a/tests/src/com/android/providers/contacts/GroupsTest.java b/tests/src/com/android/providers/contacts/GroupsTest.java
index 3d85064..15cfc71 100644
--- a/tests/src/com/android/providers/contacts/GroupsTest.java
+++ b/tests/src/com/android/providers/contacts/GroupsTest.java
@@ -16,8 +16,6 @@
 
 package com.android.providers.contacts;
 
-import com.google.android.collect.Lists;
-
 import android.accounts.Account;
 import android.content.ContentProviderOperation;
 import android.content.ContentUris;
@@ -32,9 +30,10 @@
 import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.Groups;
 import android.provider.ContactsContract.Settings;
-import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.MediumTest;
 
+import com.google.android.collect.Lists;
+
 import java.util.ArrayList;
 
 /**
diff --git a/tests/src/com/android/providers/contacts/HanziToPinyinTest.java b/tests/src/com/android/providers/contacts/HanziToPinyinTest.java
index 7ffb855..d1433ce 100644
--- a/tests/src/com/android/providers/contacts/HanziToPinyinTest.java
+++ b/tests/src/com/android/providers/contacts/HanziToPinyinTest.java
@@ -16,17 +16,17 @@
 
 package com.android.providers.contacts;
 
+import android.test.suitebuilder.annotation.SmallTest;
+
 import com.android.providers.contacts.HanziToPinyin.Token;
 
-import android.test.suitebuilder.annotation.SmallTest;
+import junit.framework.TestCase;
 
 import java.text.Collator;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Locale;
 
-import junit.framework.TestCase;
-
 @SmallTest
 public class HanziToPinyinTest extends TestCase {
     private final static String ONE_HANZI = "\u675C";
diff --git a/tests/src/com/android/providers/contacts/LegacyContactsProviderTest.java b/tests/src/com/android/providers/contacts/LegacyContactsProviderTest.java
index e515af2..c752e4e 100644
--- a/tests/src/com/android/providers/contacts/LegacyContactsProviderTest.java
+++ b/tests/src/com/android/providers/contacts/LegacyContactsProviderTest.java
@@ -16,8 +16,6 @@
 
 package com.android.providers.contacts;
 
-import com.android.providers.contacts.tests.*;
-
 import android.app.SearchManager;
 import android.content.ContentProvider;
 import android.content.ContentUris;
diff --git a/tests/src/com/android/providers/contacts/NameLookupBuilderTest.java b/tests/src/com/android/providers/contacts/NameLookupBuilderTest.java
index a5cff7a..a54193e 100644
--- a/tests/src/com/android/providers/contacts/NameLookupBuilderTest.java
+++ b/tests/src/com/android/providers/contacts/NameLookupBuilderTest.java
@@ -19,12 +19,12 @@
 import android.provider.ContactsContract.FullNameStyle;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import junit.framework.TestCase;
+
 import java.text.Collator;
 import java.util.Arrays;
 import java.util.Locale;
 
-import junit.framework.TestCase;
-
 /**
  * Unit tests for {@link NameLookupBuilder}.
  *
diff --git a/tests/src/com/android/providers/contacts/NameSplitterTest.java b/tests/src/com/android/providers/contacts/NameSplitterTest.java
index 785fb01..d9007fc 100644
--- a/tests/src/com/android/providers/contacts/NameSplitterTest.java
+++ b/tests/src/com/android/providers/contacts/NameSplitterTest.java
@@ -16,16 +16,16 @@
 
 package com.android.providers.contacts;
 
-import com.android.providers.contacts.NameSplitter.Name;
-
 import android.provider.ContactsContract.FullNameStyle;
 import android.provider.ContactsContract.PhoneticNameStyle;
 import android.test.suitebuilder.annotation.SmallTest;
 
-import java.util.Locale;
+import com.android.providers.contacts.NameSplitter.Name;
 
 import junit.framework.TestCase;
 
+import java.util.Locale;
+
 /**
  * Tests for {@link NameSplitter}.
  *
diff --git a/tests/src/com/android/providers/contacts/PhotoLoadingTestCase.java b/tests/src/com/android/providers/contacts/PhotoLoadingTestCase.java
index 20058ad..4b159a8 100644
--- a/tests/src/com/android/providers/contacts/PhotoLoadingTestCase.java
+++ b/tests/src/com/android/providers/contacts/PhotoLoadingTestCase.java
@@ -16,12 +16,12 @@
 
 package com.android.providers.contacts;
 
-import com.google.android.collect.Maps;
-
 import android.content.res.Resources;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.google.android.collect.Maps;
+
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
diff --git a/tests/src/com/android/providers/contacts/PhotoProcessorTest.java b/tests/src/com/android/providers/contacts/PhotoProcessorTest.java
new file mode 100644
index 0000000..3d882d5
--- /dev/null
+++ b/tests/src/com/android/providers/contacts/PhotoProcessorTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2012 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.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.test.AndroidTestCase;
+
+import com.android.providers.contacts.tests.R;
+
+
+/**
+ * Tests for {@link PhotoProcessor}.
+ *
+ * Most of tests are covered by {@link PhotoStoreTest}.
+ */
+public class PhotoProcessorTest extends AndroidTestCase {
+
+    public void testTransparency() {
+        final Drawable source = getTestContext().getResources().getDrawable(
+                R.drawable.transparent_10x10);
+        final Bitmap sourceBitmap = ((BitmapDrawable) source).getBitmap();
+
+        final Bitmap normalized = PhotoProcessor.getNormalizedBitmap(sourceBitmap, 50, false);
+
+        // Make sure it's not scaled up.
+        assertEquals(10, normalized.getWidth());
+        assertEquals(10, normalized.getHeight());
+
+        // Make sure the transparent pixel is now 100% white.
+        assertEquals(Color.argb(255, 255, 255, 255), normalized.getPixel(0, 0));
+    }
+}
diff --git a/tests/src/com/android/providers/contacts/PhotoStoreTest.java b/tests/src/com/android/providers/contacts/PhotoStoreTest.java
index 1550d0b..4e797f7 100644
--- a/tests/src/com/android/providers/contacts/PhotoStoreTest.java
+++ b/tests/src/com/android/providers/contacts/PhotoStoreTest.java
@@ -16,9 +16,7 @@
 
 package com.android.providers.contacts;
 
-import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
-import com.android.providers.contacts.tests.R;
-import com.android.providers.contacts.util.Hex;
+import static com.android.providers.contacts.ContactsActor.PACKAGE_GREY;
 
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
@@ -26,17 +24,17 @@
 import android.provider.ContactsContract.PhotoFiles;
 import android.test.suitebuilder.annotation.MediumTest;
 
+import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
+import com.android.providers.contacts.tests.R;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 
-import static com.android.providers.contacts.ContactsActor.PACKAGE_GREY;
-
 /**
  * Tests for {@link PhotoStore}.
  */
@@ -81,7 +79,7 @@
 
     public void testStoreNonSquare300x200Photo() throws IOException {
         // The longer side should be downscaled to the target size
-        runStorageTestForResource(R.drawable.earth_300x200, 256, 171);
+        runStorageTestForResource(R.drawable.earth_300x200, 256, 170);
     }
 
     public void testStoreNonSquare300x200PhotoWithCrop() throws IOException {
diff --git a/tests/src/com/android/providers/contacts/PostalSplitterForJapaneseTest.java b/tests/src/com/android/providers/contacts/PostalSplitterForJapaneseTest.java
index b4be173..eba9d53 100644
--- a/tests/src/com/android/providers/contacts/PostalSplitterForJapaneseTest.java
+++ b/tests/src/com/android/providers/contacts/PostalSplitterForJapaneseTest.java
@@ -16,14 +16,14 @@
 
 package com.android.providers.contacts;
 
-import com.android.providers.contacts.PostalSplitter.Postal;
-
 import android.test.suitebuilder.annotation.SmallTest;
 
-import java.util.Locale;
+import com.android.providers.contacts.PostalSplitter.Postal;
 
 import junit.framework.TestCase;
 
+import java.util.Locale;
+
 /**
  * Tests for {@link PostalSplitter}, especially for ja_JP locale.
  * This class depends on the assumption that all the tests in {@link NameSplitterTest} pass.
diff --git a/tests/src/com/android/providers/contacts/PostalSplitterTest.java b/tests/src/com/android/providers/contacts/PostalSplitterTest.java
index 6778b79..d12b3f3 100644
--- a/tests/src/com/android/providers/contacts/PostalSplitterTest.java
+++ b/tests/src/com/android/providers/contacts/PostalSplitterTest.java
@@ -16,14 +16,14 @@
 
 package com.android.providers.contacts;
 
-import com.android.providers.contacts.PostalSplitter.Postal;
-
 import android.test.suitebuilder.annotation.SmallTest;
 
-import java.util.Locale;
+import com.android.providers.contacts.PostalSplitter.Postal;
 
 import junit.framework.TestCase;
 
+import java.util.Locale;
+
 /**
  * Tests for {@link PostalSplitter}, especially for en_US locale.
  *
diff --git a/tests/src/com/android/providers/contacts/SqlInjectionDetectionTest.java b/tests/src/com/android/providers/contacts/SqlInjectionDetectionTest.java
index f4b8bab..7b3fe95 100644
--- a/tests/src/com/android/providers/contacts/SqlInjectionDetectionTest.java
+++ b/tests/src/com/android/providers/contacts/SqlInjectionDetectionTest.java
@@ -27,8 +27,6 @@
 import android.provider.ContactsContract.Contacts;
 import android.test.suitebuilder.annotation.MediumTest;
 
-import junit.framework.Assert;
-
 /**
  * Unit tests for {@link ContactsProvider2}, to make sure the queries don't allow sql injection.
  *
diff --git a/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java b/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java
index 3d28ad1..1d127c7 100644
--- a/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java
+++ b/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java
@@ -19,6 +19,9 @@
 import android.accounts.Account;
 import android.content.Context;
 import android.database.sqlite.SQLiteDatabase;
+import android.util.Log;
+
+import junit.framework.Assert;
 
 import java.util.Locale;
 
@@ -46,7 +49,7 @@
     }
 
     @Override
-    public ProfileProvider getProfileProvider() {
+    public ProfileProvider newProfileProvider() {
         return new SynchronousProfileProvider(this);
     }
 
@@ -202,6 +205,7 @@
 
     @Override
     public void wipeData() {
+        Log.i(TAG, "wipeData");
         super.wipeData();
         SQLiteDatabase db = getDatabaseHelper(getContext()).getWritableDatabase();
         db.execSQL("replace into SQLITE_SEQUENCE (name,seq) values('raw_contacts', 42)");
@@ -210,4 +214,84 @@
 
         getContactDirectoryManagerForTest().scanAllPackages();
     }
+
+    // Flags to remember which transaction callback has been called for which mode.
+    private boolean mOnBeginTransactionInternalCalledInProfileMode;
+    private boolean mOnCommitTransactionInternalCalledInProfileMode;
+    private boolean mOnRollbackTransactionInternalCalledInProfileMode;
+
+    private boolean mOnBeginTransactionInternalCalledInContactMode;
+    private boolean mOnCommitTransactionInternalCalledInContactMode;
+    private boolean mOnRollbackTransactionInternalCalledInContactMode;
+
+    public void resetTrasactionCallbackCalledFlags() {
+        mOnBeginTransactionInternalCalledInProfileMode = false;
+        mOnCommitTransactionInternalCalledInProfileMode = false;
+        mOnRollbackTransactionInternalCalledInProfileMode = false;
+
+        mOnBeginTransactionInternalCalledInContactMode = false;
+        mOnCommitTransactionInternalCalledInContactMode = false;
+        mOnRollbackTransactionInternalCalledInContactMode = false;
+    }
+
+    @Override
+    protected void onBeginTransactionInternal(boolean forProfile) {
+        super.onBeginTransactionInternal(forProfile);
+        if (forProfile) {
+            mOnBeginTransactionInternalCalledInProfileMode = true;
+        } else {
+            mOnBeginTransactionInternalCalledInContactMode = true;
+        }
+    }
+
+    @Override
+    protected void onCommitTransactionInternal(boolean forProfile) {
+        super.onCommitTransactionInternal(forProfile);
+        if (forProfile) {
+            mOnCommitTransactionInternalCalledInProfileMode = true;
+        } else {
+            mOnCommitTransactionInternalCalledInContactMode = true;
+        }
+    }
+
+    @Override
+    protected void onRollbackTransactionInternal(boolean forProfile) {
+        super.onRollbackTransactionInternal(forProfile);
+        if (forProfile) {
+            mOnRollbackTransactionInternalCalledInProfileMode = true;
+        } else {
+            mOnRollbackTransactionInternalCalledInContactMode = true;
+        }
+    }
+
+    public void assertCommitTransactionCalledForProfileMode() {
+        Assert.assertTrue("begin", mOnBeginTransactionInternalCalledInProfileMode);
+        Assert.assertTrue("commit", mOnCommitTransactionInternalCalledInProfileMode);
+        Assert.assertFalse("rollback", mOnRollbackTransactionInternalCalledInProfileMode);
+    }
+
+    public void assertRollbackTransactionCalledForProfileMode() {
+        Assert.assertTrue("begin", mOnBeginTransactionInternalCalledInProfileMode);
+        Assert.assertFalse("commit", mOnCommitTransactionInternalCalledInProfileMode);
+        Assert.assertTrue("rollback", mOnRollbackTransactionInternalCalledInProfileMode);
+    }
+
+    public void assertNoTransactionsForProfileMode() {
+        Assert.assertFalse("begin", mOnBeginTransactionInternalCalledInProfileMode);
+        Assert.assertFalse("commit", mOnCommitTransactionInternalCalledInProfileMode);
+        Assert.assertFalse("rollback", mOnRollbackTransactionInternalCalledInProfileMode);
+    }
+
+
+    public void assertCommitTransactionCalledForContactMode() {
+        Assert.assertTrue("begin", mOnBeginTransactionInternalCalledInContactMode);
+        Assert.assertTrue("commit", mOnCommitTransactionInternalCalledInContactMode);
+        Assert.assertFalse("rollback", mOnRollbackTransactionInternalCalledInContactMode);
+    }
+
+    public void assertRollbackTransactionCalledForContactMode() {
+        Assert.assertTrue("begin", mOnBeginTransactionInternalCalledInContactMode);
+        Assert.assertFalse("commit", mOnCommitTransactionInternalCalledInContactMode);
+        Assert.assertTrue("rollback", mOnRollbackTransactionInternalCalledInContactMode);
+    }
 }
diff --git a/tests/src/com/android/providers/contacts/SynchronousProfileProvider.java b/tests/src/com/android/providers/contacts/SynchronousProfileProvider.java
index 93ad70f..308e67a 100644
--- a/tests/src/com/android/providers/contacts/SynchronousProfileProvider.java
+++ b/tests/src/com/android/providers/contacts/SynchronousProfileProvider.java
@@ -16,7 +16,6 @@
 
 package com.android.providers.contacts;
 
-import android.accounts.Account;
 import android.content.Context;
 
 import java.util.Locale;
diff --git a/tests/src/com/android/providers/contacts/TestUtils.java b/tests/src/com/android/providers/contacts/TestUtils.java
index 00789bf..b6d6a27 100644
--- a/tests/src/com/android/providers/contacts/TestUtils.java
+++ b/tests/src/com/android/providers/contacts/TestUtils.java
@@ -21,12 +21,12 @@
 import android.database.Cursor;
 import android.util.Log;
 
+import junit.framework.Assert;
+
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 
-import junit.framework.Assert;
-
 public class TestUtils {
     private TestUtils() {
     }
diff --git a/tests/src/com/android/providers/contacts/VCardTest.java b/tests/src/com/android/providers/contacts/VCardTest.java
index b022ebd..820c263 100644
--- a/tests/src/com/android/providers/contacts/VCardTest.java
+++ b/tests/src/com/android/providers/contacts/VCardTest.java
@@ -16,12 +16,12 @@
 
 package com.android.providers.contacts;
 
-import com.android.vcard.VCardComposer;
-import com.android.vcard.VCardConfig;
-
 import android.content.ContentResolver;
 import android.test.suitebuilder.annotation.MediumTest;
 
+import com.android.vcard.VCardComposer;
+import com.android.vcard.VCardConfig;
+
 /**
  * Tests (or integration tests) verifying if vCard library works well with {@link ContentResolver}.
  *
diff --git a/tests/src/com/android/providers/contacts/VoicemailProviderTest.java b/tests/src/com/android/providers/contacts/VoicemailProviderTest.java
index 74195b5..8fdbccf 100644
--- a/tests/src/com/android/providers/contacts/VoicemailProviderTest.java
+++ b/tests/src/com/android/providers/contacts/VoicemailProviderTest.java
@@ -16,8 +16,6 @@
 
 package com.android.providers.contacts;
 
-import com.android.common.io.MoreCloseables;
-
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.database.Cursor;
@@ -30,6 +28,8 @@
 import android.test.MoreAsserts;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.common.io.MoreCloseables;
+
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
diff --git a/tests/src/com/android/providers/contacts/aggregation/ContactAggregatorTest.java b/tests/src/com/android/providers/contacts/aggregation/ContactAggregatorTest.java
index 795ea9c..16d06c8 100644
--- a/tests/src/com/android/providers/contacts/aggregation/ContactAggregatorTest.java
+++ b/tests/src/com/android/providers/contacts/aggregation/ContactAggregatorTest.java
@@ -16,25 +16,15 @@
 
 package com.android.providers.contacts.aggregation;
 
-import com.android.providers.contacts.BaseContactsProvider2Test;
-import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
-import com.android.providers.contacts.ContactsProvider2;
-import com.android.providers.contacts.TestUtils;
-import com.android.providers.contacts.tests.R;
-import com.google.android.collect.Lists;
-
 import android.accounts.Account;
 import android.content.ContentProviderOperation;
 import android.content.ContentProviderResult;
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
 import android.net.Uri;
-import android.provider.BaseColumns;
 import android.provider.ContactsContract;
 import android.provider.ContactsContract.AggregationExceptions;
-import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
 import android.provider.ContactsContract.CommonDataKinds.Organization;
 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
 import android.provider.ContactsContract.Contacts;
@@ -46,6 +36,11 @@
 import android.test.MoreAsserts;
 import android.test.suitebuilder.annotation.MediumTest;
 
+import com.android.providers.contacts.BaseContactsProvider2Test;
+import com.android.providers.contacts.TestUtils;
+import com.android.providers.contacts.tests.R;
+import com.google.android.collect.Lists;
+
 /**
  * Unit tests for {@link ContactAggregator}.
  *
diff --git a/tests/src/com/android/providers/contacts/aggregation/util/NameDistanceTest.java b/tests/src/com/android/providers/contacts/aggregation/util/NameDistanceTest.java
index 7f9f053..b833220 100644
--- a/tests/src/com/android/providers/contacts/aggregation/util/NameDistanceTest.java
+++ b/tests/src/com/android/providers/contacts/aggregation/util/NameDistanceTest.java
@@ -16,12 +16,11 @@
 
 package com.android.providers.contacts.aggregation.util;
 
-import com.android.providers.contacts.NameNormalizer;
-import com.android.providers.contacts.aggregation.util.NameDistance;
-import com.android.providers.contacts.util.Hex;
-
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.providers.contacts.NameNormalizer;
+import com.android.providers.contacts.util.Hex;
+
 import junit.framework.TestCase;
 
 /**
diff --git a/tests/src/com/android/providers/contacts/util/DBQueryUtilsTest.java b/tests/src/com/android/providers/contacts/util/DBQueryUtilsTest.java
index 43f7c06..7769b49 100644
--- a/tests/src/com/android/providers/contacts/util/DBQueryUtilsTest.java
+++ b/tests/src/com/android/providers/contacts/util/DBQueryUtilsTest.java
@@ -19,13 +19,13 @@
 import static com.android.providers.contacts.util.DbQueryUtils.checkForSupportedColumns;
 import static com.android.providers.contacts.util.DbQueryUtils.concatenateClauses;
 
-import com.android.common.content.ProjectionMap;
-import com.android.providers.contacts.EvenMoreAsserts;
-
 import android.content.ContentValues;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.common.content.ProjectionMap;
+import com.android.providers.contacts.EvenMoreAsserts;
+
 /**
  * Unit tests for the {@link DbQueryUtils} class.
  * Run the test like this:
diff --git a/tests/src/com/android/providers/contacts/util/MockSharedPreferences.java b/tests/src/com/android/providers/contacts/util/MockSharedPreferences.java
index d00e711..574ce19 100644
--- a/tests/src/com/android/providers/contacts/util/MockSharedPreferences.java
+++ b/tests/src/com/android/providers/contacts/util/MockSharedPreferences.java
@@ -16,10 +16,10 @@
 
 package com.android.providers.contacts.util;
 
-import com.google.android.collect.Maps;
-
 import android.content.SharedPreferences;
 
+import com.google.android.collect.Maps;
+
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
diff --git a/tests/src/com/android/providers/contacts/util/TypedUriMatcherImplTest.java b/tests/src/com/android/providers/contacts/util/TypedUriMatcherImplTest.java
index 48bd608..329e6e2 100644
--- a/tests/src/com/android/providers/contacts/util/TypedUriMatcherImplTest.java
+++ b/tests/src/com/android/providers/contacts/util/TypedUriMatcherImplTest.java
@@ -20,9 +20,6 @@
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
-import com.android.providers.contacts.util.TypedUriMatcherImpl;
-import com.android.providers.contacts.util.UriType;
-
 /**
  * Unit tests for {@link TypedUriMatcherImpl}.
  * Run the test like this: