Merge "Added 'new' badge to navigation drawer." into ub-contactsdialer-h-dev
diff --git a/proguard.flags b/proguard.flags
index e50e640..e918c62 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -44,6 +44,7 @@
 -keep class com.android.contacts.common.model.BuilderWrapper { *; }
 -keep class com.android.contacts.common.model.Contact { *; }
 -keep class com.android.contacts.common.model.ContactLoader { *; }
+-keep class com.android.contacts.common.model.Cp2DeviceLocalAccountLocator { *; }
 -keep class com.android.contacts.common.model.CPOWrapper { *; }
 -keep class com.android.contacts.common.model.dataitem.DataItem { *; }
 -keep class com.android.contacts.common.model.dataitem.DataKind { *; }
diff --git a/res/layout/fragment_sim_import.xml b/res/layout/fragment_sim_import.xml
index 95864c8..2da988e 100644
--- a/res/layout/fragment_sim_import.xml
+++ b/res/layout/fragment_sim_import.xml
@@ -49,7 +49,6 @@
         <FrameLayout
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_marginBottom="16dp"
             android:background="?android:colorBackground"
             android:elevation="4dp">
 
@@ -63,7 +62,9 @@
             <ListView
                 android:id="@+id/list"
                 android:layout_width="match_parent"
-                android:layout_height="match_parent"/>
+                android:layout_height="match_parent"
+                android:paddingTop="8dp"
+                android:clipToPadding="false"/>
 
             <android.support.v4.widget.ContentLoadingProgressBar
                 android:id="@+id/loading_progress"
diff --git a/src/com/android/contacts/SimImportFragment.java b/src/com/android/contacts/SimImportFragment.java
index bc20d62..c43caaa 100644
--- a/src/com/android/contacts/SimImportFragment.java
+++ b/src/com/android/contacts/SimImportFragment.java
@@ -26,6 +26,7 @@
 import android.os.Bundle;
 import android.support.annotation.Nullable;
 import android.support.design.widget.Snackbar;
+import android.support.v4.util.ArrayMap;
 import android.support.v4.widget.ContentLoadingProgressBar;
 import android.support.v7.widget.Toolbar;
 import android.view.LayoutInflater;
@@ -48,6 +49,8 @@
 import com.android.contacts.editor.AccountHeaderPresenter;
 
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Map;
 import java.util.Set;
 import java.util.TreeSet;
 
@@ -56,7 +59,7 @@
  * account
  */
 public class SimImportFragment extends DialogFragment
-        implements LoaderManager.LoaderCallbacks<ArrayList<SimContact>>,
+        implements LoaderManager.LoaderCallbacks<SimImportFragment.LoaderResult>,
         MultiSelectEntryContactListAdapter.SelectedContactsListener {
 
     private static final String KEY_SELECTED_IDS = "selectedIds";
@@ -86,6 +89,8 @@
         mAccountTypeManager = AccountTypeManager.getInstance(getActivity());
         mAdapter = new SimContactAdapter(getActivity());
 
+        // This needs to be set even though photos aren't loaded because the adapter assumes it
+        // will be non-null
         mAdapter.setPhotoLoader(ContactPhotoManager.getInstance(getActivity()));
         mAdapter.setDisplayCheckBoxes(true);
         mAdapter.setHasHeader(0, false);
@@ -127,13 +132,20 @@
                     .getDefaultOrBestFallback(mPreferences, mAccountTypeManager);
             mAccountHeaderPresenter.setCurrentAccount(currentDefaultAccount);
         }
+        mAccountHeaderPresenter.setObserver(new AccountHeaderPresenter.Observer() {
+            @Override
+            public void onChange(AccountHeaderPresenter sender) {
+                mAdapter.setAccount(sender.getCurrentAccount());
+            }
+        });
+        mAdapter.setAccount(mAccountHeaderPresenter.getCurrentAccount());
 
         mListView = (ListView) view.findViewById(R.id.list);
         mListView.setAdapter(mAdapter);
         mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
             @Override
             public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-                if (mAdapter.existsInContacts(position)) {
+                if (mAdapter.existsInCurrentAccount(position)) {
                     Snackbar.make(getView(), R.string.sim_import_contact_exists_toast,
                             Snackbar.LENGTH_LONG).show();
                 } else {
@@ -178,7 +190,7 @@
     public void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
         mAccountHeaderPresenter.onSaveInstanceState(outState);
-        if (mAdapter != null) {
+        if (mAdapter != null && mAdapter.mContacts != null) {
             outState.putLongArray(KEY_SELECTED_IDS, mAdapter.getSelectedContactIdsArray());
         }
     }
@@ -189,14 +201,14 @@
     }
 
     @Override
-    public void onLoadFinished(Loader<ArrayList<SimContact>> loader,
-            ArrayList<SimContact> data) {
+    public void onLoadFinished(Loader<LoaderResult> loader,
+            LoaderResult data) {
         mLoadingIndicator.hide();
         mListView.setEmptyView(getView().findViewById(R.id.empty_message));
         if (data == null) {
             return;
         }
-        mAdapter.setContacts(data);
+        mAdapter.setData(data);
         if (mSelectedContacts != null) {
             mAdapter.select(mSelectedContacts);
         } else {
@@ -205,7 +217,7 @@
     }
 
     @Override
-    public void onLoaderReset(Loader<ArrayList<SimContact>> loader) {
+    public void onLoaderReset(Loader<LoaderResult> loader) {
     }
 
     private void importCurrentSelections() {
@@ -267,7 +279,9 @@
 
     private static class SimContactAdapter extends ContactListAdapter {
         private ArrayList<SimContact> mContacts;
-        private static float DISABLED_AVATAR_ALPHA = 0.38f;
+        private AccountWithDataSet mSelectedAccount;
+        private Map<AccountWithDataSet, Set<SimContact>> mExistingMap;
+        private Map<AccountWithDataSet, TreeSet<Long>> mPerAccountCheckedIds = new ArrayMap<>();
 
         public SimContactAdapter(Context context) {
             super(context);
@@ -282,21 +296,44 @@
             super.bindView(itemView, partition, cursor, position);
             ContactListItemView contactView = (ContactListItemView) itemView;
             bindNameAndViewId(contactView, cursor);
-            bindPhoto(contactView, partition, cursor);
 
             // For accessibility. Tapping the item checks this so we don't need it to be separately
             // clickable
             contactView.getCheckBox().setFocusable(false);
             contactView.getCheckBox().setClickable(false);
-            setViewEnabled(contactView, !mContacts.get(cursor.getPosition()).existsInContacts());
+            setViewEnabled(contactView, !existsInCurrentAccount(position));
         }
 
-        public void setContacts(ArrayList<SimContact> contacts) {
-            mContacts = contacts;
+        public void setData(LoaderResult result) {
+            mContacts = result.contacts;
+            mExistingMap = result.accountsMap;
             changeCursor(SimContact.convertToContactsCursor(mContacts,
                     ContactQuery.CONTACT_PROJECTION_PRIMARY));
         }
 
+        public void setAccount(AccountWithDataSet account) {
+            if (mContacts == null) {
+                mSelectedAccount = account;
+                return;
+            }
+
+            // Save the checked state for the current account.
+            if (mSelectedAccount != null) {
+                mPerAccountCheckedIds.put(mSelectedAccount, getSelectedContactIds());
+            }
+
+            mSelectedAccount = account;
+
+            TreeSet<Long> checked = mPerAccountCheckedIds.get(mSelectedAccount);
+            if (checked == null) {
+                checked = getEnabledIdsForCurrentAccount();
+                mPerAccountCheckedIds.put(mSelectedAccount, checked);
+            }
+            setSelectedContactIds(checked);
+
+            notifyDataSetChanged();
+        }
+
         public ArrayList<SimContact> getSelectedContacts() {
             if (mContacts == null) return null;
 
@@ -315,7 +352,7 @@
 
             final TreeSet<Long> selected = new TreeSet<>();
             for (SimContact contact : mContacts) {
-                if (!contact.existsInContacts()) {
+                if (!existsInCurrentAccount(contact)) {
                     selected.add(contact.getId());
                 }
             }
@@ -330,21 +367,38 @@
             setSelectedContactIds(selected);
         }
 
-        public boolean existsInContacts(int position) {
-            return mContacts.get(position).existsInContacts();
+        public boolean existsInCurrentAccount(int position) {
+            return existsInCurrentAccount(mContacts.get(position));
+        }
+
+        public boolean existsInCurrentAccount(SimContact contact) {
+            if (mSelectedAccount == null || !mExistingMap.containsKey(mSelectedAccount)) {
+                return false;
+            }
+            return mExistingMap.get(mSelectedAccount).contains(contact);
+        }
+
+        private TreeSet<Long> getEnabledIdsForCurrentAccount() {
+            final TreeSet<Long> result = new TreeSet<>();
+            for (SimContact contact : mContacts) {
+                if (!existsInCurrentAccount(contact)) {
+                    result.add(contact.getId());
+                }
+            }
+            return result;
         }
 
         private void setViewEnabled(ContactListItemView itemView, boolean enabled) {
             itemView.getCheckBox().setEnabled(enabled);
-            itemView.getPhotoView().setAlpha(enabled ? 1f : DISABLED_AVATAR_ALPHA);
             itemView.getNameTextView().setEnabled(enabled);
         }
     }
 
-    public static class SimContactLoader extends AsyncTaskLoader<ArrayList<SimContact>> {
+
+    private static class SimContactLoader extends AsyncTaskLoader<LoaderResult> {
         private SimContactDao mDao;
         private final int mSubscriptionId;
-        private ArrayList<SimContact> mData;
+        LoaderResult mResult;
 
         public SimContactLoader(Context context, int subscriptionId) {
             super(context);
@@ -354,31 +408,42 @@
 
         @Override
         protected void onStartLoading() {
-            if (mData != null) {
-                deliverResult(mData);
+            if (mResult != null) {
+                deliverResult(mResult);
             } else {
                 forceLoad();
             }
         }
 
         @Override
-        public void deliverResult(ArrayList<SimContact> data) {
-            mData = data;
-            super.deliverResult(data);
+        public void deliverResult(LoaderResult result) {
+            mResult = result;
+            super.deliverResult(result);
         }
 
         @Override
-        public ArrayList<SimContact> loadInBackground() {
+        public LoaderResult loadInBackground() {
             final SimCard sim = mDao.getSimBySubscriptionId(mSubscriptionId);
+            LoaderResult result = new LoaderResult();
             if (sim == null) {
-                return new ArrayList<>();
+                result.contacts = new ArrayList<>();
+                result.accountsMap = Collections.emptyMap();
+                return result;
             }
-            return mDao.loadSimContactsWithExistingContactIds(sim);
+            result.contacts = mDao.loadContactsForSim(sim);
+            result.accountsMap = mDao.findAccountsOfExistingSimContacts(result.contacts);
+            return result;
         }
 
         @Override
         protected void onReset() {
-            mData = null;
+            mResult = null;
         }
+
+    }
+
+    public static class LoaderResult {
+        public ArrayList<SimContact> contacts;
+        public Map<AccountWithDataSet, Set<SimContact>> accountsMap;
     }
 }
diff --git a/src/com/android/contacts/common/Experiments.java b/src/com/android/contacts/common/Experiments.java
index e872694..dee95dd 100644
--- a/src/com/android/contacts/common/Experiments.java
+++ b/src/com/android/contacts/common/Experiments.java
@@ -53,6 +53,12 @@
     public static final String DYNAMIC_SHORTCUTS = "Shortcuts__dynamic_shortcuts";
 
     /**
+     * Experiment to enable device account detection using CP2 queries
+     */
+    public static final String OEM_CP2_DEVICE_ACCOUNT_DETECTION_ENABLED =
+            "OEM__cp2_device_account_detection_enabled";
+
+    /**
      * Experiment to toggle contacts sync using the pull to refresh gesture.
      */
     public static final String PULL_TO_REFRESH = "PullToRefresh__pull_to_refresh";
diff --git a/src/com/android/contacts/common/database/SimContactDao.java b/src/com/android/contacts/common/database/SimContactDao.java
index cab2906..ee36645 100644
--- a/src/com/android/contacts/common/database/SimContactDao.java
+++ b/src/com/android/contacts/common/database/SimContactDao.java
@@ -30,7 +30,10 @@
 import android.provider.BaseColumns;
 import android.provider.ContactsContract;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.RawContacts;
 import android.support.annotation.VisibleForTesting;
+import android.support.v4.util.ArrayMap;
+import android.support.v4.util.ArraySet;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -50,7 +53,10 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * Provides data access methods for loading contacts from a SIM card and and migrating these
@@ -149,10 +155,6 @@
         return loadFrom(ICC_CONTENT_URI);
     }
 
-    public ArrayList<SimContact> loadSimContactsWithExistingContactIds(SimCard sim) {
-        return getSimContactsWithRawContacts(sim);
-    }
-
     public ContentProviderResult[] importContacts(List<SimContact> contacts,
             AccountWithDataSet targetAccount)
             throws RemoteException, OperationApplicationException {
@@ -193,6 +195,62 @@
         return null;
     }
 
+    public Map<AccountWithDataSet, Set<SimContact>> findAccountsOfExistingSimContacts(
+            List<SimContact> contacts) {
+        final Map<AccountWithDataSet, Set<SimContact>> result = new ArrayMap<>();
+        for (int i = 0; i < contacts.size(); i += IMPORT_MAX_BATCH_SIZE) {
+            findAccountsOfExistingSimContacts(
+                    contacts.subList(i, Math.min(contacts.size(), i + IMPORT_MAX_BATCH_SIZE)),
+                    result);
+        }
+        return result;
+    }
+
+    private void findAccountsOfExistingSimContacts(List<SimContact> contacts,
+            Map<AccountWithDataSet, Set<SimContact>> result) {
+        final Map<Long, List<SimContact>> rawContactToSimContact = new HashMap<>();
+        Collections.sort(contacts, SimContact.compareByPhoneThenName());
+
+        final Cursor dataCursor = queryRawContactsForSimContacts(contacts);
+
+        try {
+            while (dataCursor.moveToNext()) {
+                final String number = DataQuery.getPhoneNumber(dataCursor);
+                final String name = DataQuery.getDisplayName(dataCursor);
+
+                final int index = SimContact.findByPhoneAndName(contacts, number, name);
+                if (index < 0) {
+                    continue;
+                }
+                final SimContact contact = contacts.get(index);
+                final long id = DataQuery.getRawContactId(dataCursor);
+                if (!rawContactToSimContact.containsKey(id)) {
+                    rawContactToSimContact.put(id, new ArrayList<SimContact>());
+                }
+                rawContactToSimContact.get(id).add(contact);
+            }
+        } finally {
+            dataCursor.close();
+        }
+
+        final Cursor accountsCursor = queryAccountsOfRawContacts(rawContactToSimContact.keySet());
+        try {
+            while (accountsCursor.moveToNext()) {
+                final AccountWithDataSet account = AccountQuery.getAccount(accountsCursor);
+                final long id = AccountQuery.getId(accountsCursor);
+                if (!result.containsKey(account)) {
+                    result.put(account, new ArraySet<SimContact>());
+                }
+                for (SimContact contact : rawContactToSimContact.get(id)) {
+                    result.get(account).add(contact);
+                }
+            }
+        } finally {
+            accountsCursor.close();
+        }
+    }
+
+
     private ContentProviderResult[] importBatch(List<SimContact> contacts,
             AccountWithDataSet targetAccount)
             throws RemoteException, OperationApplicationException {
@@ -207,9 +265,6 @@
                 mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
         final List<SubscriptionInfo> subscriptions = subscriptionManager
                 .getActiveSubscriptionInfoList();
-        if (subscriptions == null) {
-            return Collections.emptyList();
-        }
         final ArrayList<SimCard> result = new ArrayList<>();
         for (SubscriptionInfo subscriptionInfo : subscriptions) {
             result.add(SimCard.create(subscriptionInfo));
@@ -252,19 +307,7 @@
         return result;
     }
 
-    private ArrayList<SimContact> getSimContactsWithRawContacts(SimCard sim) {
-        final ArrayList<SimContact> contacts = new ArrayList<>(getContactsForSim(sim));
-        for (int i = 0; i < contacts.size(); i += DataQuery.MAX_BATCH_SIZE) {
-            final List<SimContact> batch =
-                    contacts.subList(i, Math.min(i + DataQuery.MAX_BATCH_SIZE, contacts.size()));
-            setRawContactsForSimContacts(batch);
-        }
-        // Restore default sort order
-        Collections.sort(contacts, SimContact.compareById());
-        return contacts;
-    }
-
-    private void setRawContactsForSimContacts(List<SimContact> contacts) {
+    private Cursor queryRawContactsForSimContacts(List<SimContact> contacts) {
         final StringBuilder selectionBuilder = new StringBuilder();
 
         int phoneCount = 0;
@@ -287,47 +330,32 @@
             }
         }
 
-        final Cursor cursor = mResolver.query(ContactsContract.Data.CONTENT_URI.buildUpon()
+        return mResolver.query(ContactsContract.Data.CONTENT_URI.buildUpon()
                         .appendQueryParameter(ContactsContract.Data.VISIBLE_CONTACTS_ONLY, "true")
                         .build(),
                 DataQuery.PROJECTION,
                 selectionBuilder.toString(),
                 selectionArgs.toArray(new String[selectionArgs.size()]),
-                ContactsContract.Data.RAW_CONTACT_ID + " ASC");
-
-        if (cursor == null) {
-            initializeRawContactIds(contacts);
-            return;
-        }
-
-        try {
-            setRawContactsForSimContacts(contacts, cursor);
-        } finally {
-            cursor.close();
-        }
+                null);
     }
 
-    private void initializeRawContactIds(List<SimContact> contacts) {
-        for (int i = 0; i < contacts.size(); i++) {
-            contacts.set(i, contacts.get(i).withRawContactId(SimContact.NO_EXISTING_CONTACT));
+    private Cursor queryAccountsOfRawContacts(Set<Long> ids) {
+        final StringBuilder selectionBuilder = new StringBuilder();
+
+        final String[] args = new String[ids.size()];
+
+        selectionBuilder.append(RawContacts._ID).append(" IN (")
+                .append(Joiner.on(',').join(Collections.nCopies(args.length, '?')))
+                .append(")");
+        int i = 0;
+        for (long id : ids) {
+            args[i++] = String.valueOf(id);
         }
-    }
-
-    private void setRawContactsForSimContacts(List<SimContact> contacts, Cursor cursor) {
-        initializeRawContactIds(contacts);
-        Collections.sort(contacts, SimContact.compareByPhoneThenName());
-
-        while (cursor.moveToNext()) {
-            final String number = DataQuery.getPhoneNumber(cursor);
-            final String name = DataQuery.getDisplayName(cursor);
-
-            int index = SimContact.findByPhoneAndName(contacts, number, name);
-            if (index < 0) {
-                continue;
-            }
-            final SimContact contact = contacts.get(index);
-            contacts.set(index, contact.withRawContactId(DataQuery.getRawContactId(cursor)));
-        }
+        return mResolver.query(RawContacts.CONTENT_URI,
+                AccountQuery.PROJECTION,
+                selectionBuilder.toString(),
+                args,
+                null);
     }
 
     private ArrayList<ContentProviderOperation> createImportOperations(List<SimContact> contacts,
@@ -443,4 +471,20 @@
             return cursor.getString(DISPLAY_NAME);
         }
     }
+
+    private static final class AccountQuery {
+        public static final String[] PROJECTION = new String[] {
+                RawContacts._ID, RawContacts.ACCOUNT_NAME, RawContacts.ACCOUNT_TYPE,
+                RawContacts.DATA_SET
+        };
+
+        public static long getId(Cursor cursor) {
+            return cursor.getLong(0);
+        }
+
+        public static AccountWithDataSet getAccount(Cursor cursor) {
+            return new AccountWithDataSet(cursor.getString(1), cursor.getString(2),
+                    cursor.getString(3));
+        }
+    }
 }
diff --git a/src/com/android/contacts/common/list/FavoritesAndContactsLoader.java b/src/com/android/contacts/common/list/FavoritesAndContactsLoader.java
index 2dcbc18..e112846 100644
--- a/src/com/android/contacts/common/list/FavoritesAndContactsLoader.java
+++ b/src/com/android/contacts/common/list/FavoritesAndContactsLoader.java
@@ -47,9 +47,6 @@
     private CountDownLatch mAutocompleteLatch = new CountDownLatch(1);
     private Cursor mAutocompleteCursor;
     private int mAutocompleteTimeout;
-    // If we didn't get anything back from autocomplete and we've fallen back to CP2,
-    // we can't wait for the Experiments.SEARCH_YENTA_TIMEOUT_MILLIS everytime the query changes.
-    private boolean mAutocompleteFallback;
 
     public FavoritesAndContactsLoader(Context context) {
         super(context);
@@ -74,27 +71,14 @@
     @Override
     public Cursor loadInBackground() {
         List<Cursor> cursors = Lists.newArrayList();
-
-        // Load favorites
         if (mLoadFavorites) {
             cursors.add(loadFavoritesContacts());
         }
 
-        // Load contacts
-        final Cursor contactsCursor;
-        if (mAutocompleteQuery == null || mAutocompleteFallback) {
-            // Query CP2 normally
-            contactsCursor = loadContacts();
-            cursors.add(contactsCursor);
-        } else {
+        if (mAutocompleteQuery != null) {
             final AutocompleteHelper autocompleteHelper =
                     ObjectFactory.getAutocompleteHelper(getContext());
-            if (autocompleteHelper == null) {
-                // Fallback to CP2, the flag is on but we couldn't instantiate autocomplete
-                contactsCursor = loadContacts();
-                cursors.add(contactsCursor);
-                mAutocompleteFallback = true;
-            } else {
+            if (autocompleteHelper != null) {
                 autocompleteHelper.setListener(this);
                 autocompleteHelper.setProjection(mProjection);
                 autocompleteHelper.setQuery(mAutocompleteQuery);
@@ -105,18 +89,21 @@
                 } catch (InterruptedException e) {
                     logw("Interrupted while waiting for autocompletions");
                 }
-                if (mAutocompleteCursor != null && mAutocompleteCursor.getCount() > 0) {
-                    contactsCursor = null;
+                if (mAutocompleteCursor != null) {
                     cursors.add(mAutocompleteCursor);
-                } else {
-                    // Fallback to CP2, we didn't get anything back from autocomplete
-                    contactsCursor = loadContacts();
-                    cursors.add(loadContacts());
-                    mAutocompleteFallback = true;
+                    // TODO: exclude these results from the main loader results, see b/30742359
                 }
             }
         }
 
+        // TODO: if the autocomplete experiment in on, only show those results even if they're empty
+        final Cursor contactsCursor = mAutocompleteQuery == null ? loadContacts() : null;
+        if (mAutocompleteQuery == null) {
+            cursors.add(contactsCursor);
+        }
+        // Guard against passing an empty array to the MergeCursor constructor
+        if (cursors.isEmpty()) cursors.add(null);
+
         return new MergeCursor(cursors.toArray(new Cursor[cursors.size()])) {
             @Override
             public Bundle getExtras() {
diff --git a/src/com/android/contacts/common/model/AccountTypeManager.java b/src/com/android/contacts/common/model/AccountTypeManager.java
index 4b0bcc1..15c9771 100644
--- a/src/com/android/contacts/common/model/AccountTypeManager.java
+++ b/src/com/android/contacts/common/model/AccountTypeManager.java
@@ -669,11 +669,10 @@
             }
         }
 
-        final DeviceLocalAccountLocator deviceAccounts =
-                new DeviceLocalAccountLocator(mContext.getContentResolver(),
-                        mDeviceLocalAccountTypeFactory,
-                        allAccounts);
-        final List<AccountWithDataSet> localAccounts = deviceAccounts.getDeviceLocalAccounts();
+        final DeviceLocalAccountLocator deviceAccountLocator = DeviceLocalAccountLocator
+                .create(mContext, allAccounts);
+        final List<AccountWithDataSet> localAccounts = deviceAccountLocator
+                .getDeviceLocalAccounts();
         allAccounts.addAll(localAccounts);
 
         for (AccountWithDataSet localAccount : localAccounts) {
diff --git a/src/com/android/contacts/common/model/Cp2DeviceLocalAccountLocator.java b/src/com/android/contacts/common/model/Cp2DeviceLocalAccountLocator.java
new file mode 100644
index 0000000..64f7c03
--- /dev/null
+++ b/src/com/android/contacts/common/model/Cp2DeviceLocalAccountLocator.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2016 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.contacts.common.model;
+
+import android.accounts.AccountManager;
+import android.content.ContentResolver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.support.annotation.VisibleForTesting;
+
+import com.android.contacts.common.model.account.AccountWithDataSet;
+import com.android.contacts.common.util.DeviceLocalAccountTypeFactory;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Attempts to create accounts for "Device" contacts by querying
+ * CP2 for records with {@link android.provider.ContactsContract.RawContacts#ACCOUNT_TYPE} columns
+ * that do not exist for any account returned by {@link AccountManager#getAccounts()}
+ *
+ * This class should be used from a background thread since it does DB queries
+ */
+public class Cp2DeviceLocalAccountLocator extends DeviceLocalAccountLocator {
+
+    // Note this class is assuming ACCOUNT_NAME and ACCOUNT_TYPE have same values in
+    // RawContacts, Groups, and Settings. This assumption simplifies the code somewhat and it
+    // is true right now and unlikely to ever change.
+    @VisibleForTesting
+    static String[] PROJECTION = new String[] {
+            ContactsContract.RawContacts.ACCOUNT_NAME, ContactsContract.RawContacts.ACCOUNT_TYPE,
+            ContactsContract.RawContacts.DATA_SET
+    };
+
+    private static final int COL_NAME = 0;
+    private static final int COL_TYPE = 1;
+    private static final int COL_DATA_SET = 2;
+
+    private final ContentResolver mResolver;
+    private final DeviceLocalAccountTypeFactory mAccountTypeFactory;
+
+    private final String mSelection;
+    private final String[] mSelectionArgs;
+
+    public Cp2DeviceLocalAccountLocator(ContentResolver contentResolver,
+            DeviceLocalAccountTypeFactory factory,
+            List<AccountWithDataSet> knownAccounts) {
+        mResolver = contentResolver;
+        mAccountTypeFactory = factory;
+
+        final Set<String> knownAccountTypes = new HashSet<>();
+        for (AccountWithDataSet account : knownAccounts) {
+            knownAccountTypes.add(account.type);
+        }
+        mSelection = getSelection(knownAccountTypes);
+        mSelectionArgs = getSelectionArgs(knownAccountTypes);
+    }
+
+    @Override
+    public List<AccountWithDataSet> getDeviceLocalAccounts() {
+
+        final Set<AccountWithDataSet> localAccounts = new HashSet<>();
+
+        // Many device accounts have default groups associated with them.
+        addAccountsFromQuery(ContactsContract.Groups.CONTENT_URI, localAccounts);
+        addAccountsFromQuery(ContactsContract.Settings.CONTENT_URI, localAccounts);
+        addAccountsFromQuery(ContactsContract.RawContacts.CONTENT_URI, localAccounts);
+
+        return new ArrayList<>(localAccounts);
+    }
+
+    private void addAccountsFromQuery(Uri uri, Set<AccountWithDataSet> accounts) {
+        final Cursor cursor = mResolver.query(uri, PROJECTION, mSelection, mSelectionArgs, null);
+
+        if (cursor == null) return;
+
+        try {
+            addAccountsFromCursor(cursor, accounts);
+        } finally {
+            cursor.close();
+        }
+    }
+
+    private void addAccountsFromCursor(Cursor cursor, Set<AccountWithDataSet> accounts) {
+        while (cursor.moveToNext()) {
+            final String name = cursor.getString(COL_NAME);
+            final String type = cursor.getString(COL_TYPE);
+            final String dataSet = cursor.getString(COL_DATA_SET);
+
+            if (DeviceLocalAccountTypeFactory.Util.isLocalAccountType(
+                    mAccountTypeFactory, type)) {
+                accounts.add(new AccountWithDataSet(name, type, dataSet));
+            }
+        }
+    }
+
+    @VisibleForTesting
+    public String getSelection() {
+        return mSelection;
+    }
+
+    @VisibleForTesting
+    public String[] getSelectionArgs() {
+        return mSelectionArgs;
+    }
+
+    private static String getSelection(Set<String> knownAccountTypes) {
+        final StringBuilder sb = new StringBuilder()
+                .append(ContactsContract.RawContacts.ACCOUNT_TYPE).append(" IS NULL");
+        if (knownAccountTypes.isEmpty()) {
+            return sb.toString();
+        }
+        sb.append(" OR ").append(ContactsContract.RawContacts.ACCOUNT_TYPE).append(" NOT IN (");
+        for (String ignored : knownAccountTypes) {
+            sb.append("?,");
+        }
+        // Remove trailing ','
+        sb.deleteCharAt(sb.length() - 1).append(')');
+        return sb.toString();
+    }
+
+    private static String[] getSelectionArgs(Set<String> knownAccountTypes) {
+        if (knownAccountTypes.isEmpty()) return null;
+
+        return knownAccountTypes.toArray(new String[knownAccountTypes.size()]);
+    }
+}
diff --git a/src/com/android/contacts/common/model/DeviceLocalAccountLocator.java b/src/com/android/contacts/common/model/DeviceLocalAccountLocator.java
index 8c45c40..17f5f5e 100644
--- a/src/com/android/contacts/common/model/DeviceLocalAccountLocator.java
+++ b/src/com/android/contacts/common/model/DeviceLocalAccountLocator.java
@@ -15,128 +15,41 @@
  */
 package com.android.contacts.common.model;
 
-import android.accounts.AccountManager;
-import android.content.ContentResolver;
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.ContactsContract;
-import android.support.annotation.VisibleForTesting;
+import android.content.Context;
 
+import com.android.contacts.common.Experiments;
 import com.android.contacts.common.model.account.AccountWithDataSet;
-import com.android.contacts.common.util.DeviceLocalAccountTypeFactory;
+import com.android.contactsbind.ObjectFactory;
+import com.android.contactsbind.experiments.Flags;
 
-import java.util.ArrayList;
-import java.util.HashSet;
+import java.util.Collections;
 import java.util.List;
-import java.util.Set;
 
 /**
- * DeviceLocalAccountLocator attempts to create accounts for "Device" contacts by querying
- * CP2 for records with {@link android.provider.ContactsContract.RawContacts#ACCOUNT_TYPE} columns
- * that do not exist for any account returned by {@link AccountManager#getAccounts()}
- *
- * This class should be used from a background thread since it does DB queries
+ * Attempts to detect accounts for device contacts
  */
-public class DeviceLocalAccountLocator {
+public abstract class DeviceLocalAccountLocator {
 
-    // Note this class is assuming ACCOUNT_NAME and ACCOUNT_TYPE have same values in
-    // RawContacts, Groups, and Settings. This assumption simplifies the code somewhat and it
-    // is true right now and unlikely to ever change.
-    @VisibleForTesting
-    static String[] PROJECTION = new String[] {
-            ContactsContract.RawContacts.ACCOUNT_NAME, ContactsContract.RawContacts.ACCOUNT_TYPE,
-            ContactsContract.RawContacts.DATA_SET
+    /**
+     * Returns a list of device local accounts
+     */
+    public abstract List<AccountWithDataSet> getDeviceLocalAccounts();
+
+    // This works on Nexus and AOSP because the local device account is the null account but most
+    // OEMs have a special account name and type for their device account.
+    public static final DeviceLocalAccountLocator NULL_ONLY = new DeviceLocalAccountLocator() {
+        @Override
+        public List<AccountWithDataSet> getDeviceLocalAccounts() {
+            return Collections.singletonList(AccountWithDataSet.getNullAccount());
+        }
     };
 
-    private static final int COL_NAME = 0;
-    private static final int COL_TYPE = 1;
-    private static final int COL_DATA_SET = 2;
-
-    private final ContentResolver mResolver;
-    private final DeviceLocalAccountTypeFactory mAccountTypeFactory;
-
-    private final String mSelection;
-    private final String[] mSelectionArgs;
-
-    public DeviceLocalAccountLocator(ContentResolver contentResolver,
-            DeviceLocalAccountTypeFactory factory,
+    public static DeviceLocalAccountLocator create(Context context,
             List<AccountWithDataSet> knownAccounts) {
-        mResolver = contentResolver;
-        mAccountTypeFactory = factory;
-
-        final Set<String> knownAccountTypes = new HashSet<>();
-        for (AccountWithDataSet account : knownAccounts) {
-            knownAccountTypes.add(account.type);
+        if (Flags.getInstance().getBoolean(Experiments.OEM_CP2_DEVICE_ACCOUNT_DETECTION_ENABLED)) {
+            return new Cp2DeviceLocalAccountLocator(context.getContentResolver(),
+                    ObjectFactory.getDeviceLocalAccountTypeFactory(context), knownAccounts);
         }
-        mSelection = getSelection(knownAccountTypes);
-        mSelectionArgs = getSelectionArgs(knownAccountTypes);
-    }
-
-    public List<AccountWithDataSet> getDeviceLocalAccounts() {
-
-        final Set<AccountWithDataSet> localAccounts = new HashSet<>();
-
-        // Many device accounts have default groups associated with them.
-        addAccountsFromQuery(ContactsContract.Groups.CONTENT_URI, localAccounts);
-        addAccountsFromQuery(ContactsContract.Settings.CONTENT_URI, localAccounts);
-        addAccountsFromQuery(ContactsContract.RawContacts.CONTENT_URI, localAccounts);
-
-        return new ArrayList<>(localAccounts);
-    }
-
-    private void addAccountsFromQuery(Uri uri, Set<AccountWithDataSet> accounts) {
-        final Cursor cursor = mResolver.query(uri, PROJECTION, mSelection, mSelectionArgs, null);
-
-        if (cursor == null) return;
-
-        try {
-            addAccountsFromCursor(cursor, accounts);
-        } finally {
-            cursor.close();
-        }
-    }
-
-    private void addAccountsFromCursor(Cursor cursor, Set<AccountWithDataSet> accounts) {
-        while (cursor.moveToNext()) {
-            final String name = cursor.getString(COL_NAME);
-            final String type = cursor.getString(COL_TYPE);
-            final String dataSet = cursor.getString(COL_DATA_SET);
-
-            if (DeviceLocalAccountTypeFactory.Util.isLocalAccountType(
-                    mAccountTypeFactory, type)) {
-                accounts.add(new AccountWithDataSet(name, type, dataSet));
-            }
-        }
-    }
-
-    @VisibleForTesting
-    public String getSelection() {
-        return mSelection;
-    }
-
-    @VisibleForTesting
-    public String[] getSelectionArgs() {
-        return mSelectionArgs;
-    }
-
-    private static String getSelection(Set<String> knownAccountTypes) {
-        final StringBuilder sb = new StringBuilder()
-                .append(ContactsContract.RawContacts.ACCOUNT_TYPE).append(" IS NULL");
-        if (knownAccountTypes.isEmpty()) {
-            return sb.toString();
-        }
-        sb.append(" OR ").append(ContactsContract.RawContacts.ACCOUNT_TYPE).append(" NOT IN (");
-        for (String ignored : knownAccountTypes) {
-            sb.append("?,");
-        }
-        // Remove trailing ','
-        sb.deleteCharAt(sb.length() - 1).append(')');
-        return sb.toString();
-    }
-
-    private static String[] getSelectionArgs(Set<String> knownAccountTypes) {
-        if (knownAccountTypes.isEmpty()) return null;
-
-        return knownAccountTypes.toArray(new String[knownAccountTypes.size()]);
+        return NULL_ONLY;
     }
 }
diff --git a/src/com/android/contacts/common/model/SimContact.java b/src/com/android/contacts/common/model/SimContact.java
index 3768503..25af5f8 100644
--- a/src/com/android/contacts/common/model/SimContact.java
+++ b/src/com/android/contacts/common/model/SimContact.java
@@ -40,27 +40,18 @@
  * Holds data for contacts loaded from the SIM card.
  */
 public class SimContact implements Parcelable {
-    public static final long EXISTING_CONTACT_UNINITIALIZED = -2;
-    public static final long NO_EXISTING_CONTACT = -1;
-
     private final long mId;
     private final String mName;
     private final String mPhone;
     private final String[] mEmails;
 
-    private final long mRawContactId;
-
     public SimContact(long id, String name, String phone, String[] emails) {
-        this(id, name, phone, emails, EXISTING_CONTACT_UNINITIALIZED);
-    }
-
-    public SimContact(long id, String name, String phone, String[] emails, long rawContactId) {
         mId = id;
         mName = name;
         mPhone = phone;
         mEmails = emails;
-        mRawContactId = rawContactId;
     }
+
     public long getId() {
         return mId;
     }
@@ -77,13 +68,6 @@
         return mEmails;
     }
 
-    public boolean existsInContacts() {
-        if (mRawContactId == EXISTING_CONTACT_UNINITIALIZED) {
-            throw new IllegalStateException("Raw contact ID is uninitialized");
-        }
-        return mRawContactId > 0;
-    }
-
     public void appendCreateContactOperations(List<ContentProviderOperation> ops,
             AccountWithDataSet targetAccount) {
         // nothing to save.
@@ -139,10 +123,6 @@
         return mEmails != null && mEmails.length > 0;
     }
 
-    public SimContact withRawContactId(long id) {
-        return new SimContact(mId, mName, mPhone, mEmails, id);
-    }
-
     /**
      * Generate a "fake" lookup key. This is needed because
      * {@link com.android.contacts.common.ContactPhotoManager} will only generate a letter avatar
@@ -165,7 +145,6 @@
                 ", mName='" + mName + '\'' +
                 ", mPhone='" + mPhone + '\'' +
                 ", mEmails=" + Arrays.toString(mEmails) +
-                ", mRawContactId=" + mRawContactId +
                 '}';
     }
 
@@ -176,9 +155,8 @@
 
         final SimContact that = (SimContact) o;
 
-        return mId == that.mId && mRawContactId == that.mRawContactId
-                && Objects.equals(mName, that.mName) && Objects.equals(mPhone, that.mPhone)
-                && Arrays.equals(mEmails, that.mEmails);
+        return mId == that.mId && Objects.equals(mName, that.mName) &&
+                Objects.equals(mPhone, that.mPhone) && Arrays.equals(mEmails, that.mEmails);
     }
 
     @Override
@@ -187,7 +165,6 @@
         result = 31 * result + (mName != null ? mName.hashCode() : 0);
         result = 31 * result + (mPhone != null ? mPhone.hashCode() : 0);
         result = 31 * result + Arrays.hashCode(mEmails);
-        result = 31 * result + (int) (mRawContactId ^ (mRawContactId >>> 32));
         return result;
     }
 
@@ -202,9 +179,24 @@
         dest.writeString(mName);
         dest.writeString(mPhone);
         dest.writeStringArray(mEmails);
-        dest.writeLong(mRawContactId);
     }
 
+    public static final Creator<SimContact> CREATOR = new Creator<SimContact>() {
+        @Override
+        public SimContact createFromParcel(Parcel source) {
+            final long id = source.readLong();
+            final String name = source.readString();
+            final String phone = source.readString();
+            final String[] emails = source.createStringArray();
+            return new SimContact(id, name, phone, emails);
+        }
+
+        @Override
+        public SimContact[] newArray(int size) {
+            return new SimContact[size];
+        }
+    };
+
     /**
      * Convert a collection of SIM contacts to a Cursor matching a query from
      * {@link android.provider.ContactsContract.Contacts#CONTENT_URI} with the provided projection.
@@ -221,23 +213,6 @@
         return result;
     }
 
-    public static final Creator<SimContact> CREATOR = new Creator<SimContact>() {
-        @Override
-        public SimContact createFromParcel(Parcel source) {
-            final long id = source.readLong();
-            final String name = source.readString();
-            final String phone = source.readString();
-            final String[] emails = source.createStringArray();
-            final long contactId = source.readLong();
-            return new SimContact(id, name, phone, emails, contactId);
-        }
-
-        @Override
-        public SimContact[] newArray(int size) {
-            return new SimContact[size];
-        }
-    };
-
     /**
      * Returns the index of a contact with a matching name and phone
      * @param contacts list to search. Should be sorted using
@@ -246,8 +221,8 @@
      * @param name the name to search for
      */
     public static int findByPhoneAndName(List<SimContact> contacts, String phone, String name) {
-        return Collections.binarySearch(contacts, new SimContact(-1, name, phone, null,
-                NO_EXISTING_CONTACT), compareByPhoneThenName());
+        return Collections.binarySearch(contacts, new SimContact(-1, name, phone, null),
+                compareByPhoneThenName());
     }
 
     public static final Comparator<SimContact> compareByPhoneThenName() {
diff --git a/src/com/android/contacts/common/model/account/AccountWithDataSet.java b/src/com/android/contacts/common/model/account/AccountWithDataSet.java
index 37f6652..3ee0aab 100644
--- a/src/com/android/contacts/common/model/account/AccountWithDataSet.java
+++ b/src/com/android/contacts/common/model/account/AccountWithDataSet.java
@@ -243,9 +243,13 @@
     public static AccountWithDataSet getDefaultOrBestFallback(ContactsPreferences preferences,
             AccountTypeManager accountTypeManager) {
         if (preferences.isDefaultAccountSet()) {
-            return preferences.getDefaultAccount();
+            final AccountWithDataSet account = preferences.getDefaultAccount();
+            if (accountTypeManager.contains(account, true)) {
+                return account;
+            }
         }
-        List<AccountWithDataSet> accounts = accountTypeManager.getAccounts(/* writableOnly */ true);
+        final List<AccountWithDataSet> accounts = accountTypeManager
+                .getAccounts(/* writableOnly */ true);
 
         if (accounts.isEmpty()) {
             return AccountWithDataSet.getNullAccount();
diff --git a/tests/src/com/android/contacts/common/database/SimContactDaoTests.java b/tests/src/com/android/contacts/common/database/SimContactDaoTests.java
index d0f8990..7250593 100644
--- a/tests/src/com/android/contacts/common/database/SimContactDaoTests.java
+++ b/tests/src/com/android/contacts/common/database/SimContactDaoTests.java
@@ -1,358 +1,359 @@
-///*
-// * Copyright (C) 2016 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.contacts.common.database;
-//
-//import android.content.ContentProviderOperation;
-//import android.content.ContentResolver;
-//import android.content.Context;
-//import android.database.Cursor;
-//import android.database.CursorWrapper;
-//import android.database.DatabaseUtils;
-//import android.provider.ContactsContract;
-//import android.support.annotation.RequiresApi;
-//import android.support.test.InstrumentationRegistry;
-//import android.support.test.filters.LargeTest;
-//import android.support.test.filters.SdkSuppress;
-//import android.support.test.filters.Suppress;
-//import android.support.test.runner.AndroidJUnit4;
-//
-//import com.android.contacts.common.model.SimContact;
-//import com.android.contacts.common.model.account.AccountWithDataSet;
-//import com.android.contacts.tests.AccountsTestHelper;
-//import com.android.contacts.tests.SimContactsTestHelper;
-//
-//import org.hamcrest.BaseMatcher;
-//import org.hamcrest.Description;
-//import org.hamcrest.Matcher;
-//import org.junit.After;
-//import org.junit.Before;
-//import org.junit.Test;
-//import org.junit.experimental.runners.Enclosed;
-//import org.junit.runner.RunWith;
-//
-//import java.util.ArrayList;
-//import java.util.Arrays;
-//
-//import static android.os.Build.VERSION_CODES;
-//import static org.hamcrest.Matchers.allOf;
-//import static org.junit.Assert.assertThat;
-//
-//@RunWith(Enclosed.class)
-//public class SimContactDaoTests {
-//
-//    // Lollipop MR1 required for AccountManager.removeAccountExplicitly
-//    @RequiresApi(api = VERSION_CODES.LOLLIPOP_MR1)
-//    @SdkSuppress(minSdkVersion = VERSION_CODES.LOLLIPOP_MR1)
-//    @LargeTest
-//    @RunWith(AndroidJUnit4.class)
-//    public static class ImportIntegrationTest {
-//        private AccountWithDataSet mAccount;
-//        private AccountsTestHelper mAccountsHelper;
-//        private ContentResolver mResolver;
-//
-//        @Before
-//        public void setUp() throws Exception {
-//            mAccountsHelper = new AccountsTestHelper();
-//            mAccount = mAccountsHelper.addTestAccount();
-//            mResolver = getContext().getContentResolver();
-//        }
-//
-//        @After
-//        public void tearDown() throws Exception {
-//            mAccountsHelper.cleanup();
-//        }
-//
-//        @Test
-//        public void importFromSim() throws Exception {
-//            final SimContactDao sut = new SimContactDao(getContext());
-//
-//            sut.importContacts(Arrays.asList(
-//                    new SimContact(1, "Test One", "15095550101", null),
-//                    new SimContact(2, "Test Two", "15095550102", null),
-//                    new SimContact(3, "Test Three", "15095550103", new String[] {
-//                            "user@example.com", "user2@example.com"
-//                    })
-//            ), mAccount);
-//
-//            Cursor cursor = queryContactWithName("Test One");
-//            assertThat(cursor, hasCount(2));
-//            assertThat(cursor, hasName("Test One"));
-//            assertThat(cursor, hasPhone("15095550101"));
-//            cursor.close();
-//
-//            cursor = queryContactWithName("Test Two");
-//            assertThat(cursor, hasCount(2));
-//            assertThat(cursor, hasName("Test Two"));
-//            assertThat(cursor, hasPhone("15095550102"));
-//            cursor.close();
-//
-//            cursor = queryContactWithName("Test Three");
-//            assertThat(cursor, hasCount(4));
-//            assertThat(cursor, hasName("Test Three"));
-//            assertThat(cursor, hasPhone("15095550103"));
-//            assertThat(cursor, allOf(hasEmail("user@example.com"), hasEmail("user2@example.com")));
-//            cursor.close();
-//        }
-//
-//        @Test
-//        public void importContactWhichOnlyHasName() throws Exception {
-//            final SimContactDao sut = new SimContactDao(getContext());
-//
-//            sut.importContacts(Arrays.asList(
-//                    new SimContact(1, "Test importJustName", null, null)
-//            ), mAccount);
-//
-//            Cursor cursor = queryAllDataInAccount();
-//
-//            assertThat(cursor, hasCount(1));
-//            assertThat(cursor, hasName("Test importJustName"));
-//            cursor.close();
-//        }
-//
-//        @Test
-//        public void importContactWhichOnlyHasPhone() throws Exception {
-//            final SimContactDao sut = new SimContactDao(getContext());
-//
-//            sut.importContacts(Arrays.asList(
-//                    new SimContact(1, null, "15095550111", null)
-//            ), mAccount);
-//
-//            Cursor cursor = queryAllDataInAccount();
-//
-//            assertThat(cursor, hasCount(1));
-//            assertThat(cursor, hasPhone("15095550111"));
-//            cursor.close();
-//        }
-//
-//        @Test
-//        public void ignoresEmptyContacts() throws Exception {
-//            final SimContactDao sut = new SimContactDao(getContext());
-//
-//            // This probably isn't possible but we'll test it to demonstrate expected behavior and
-//            // just in case it does occur
-//            sut.importContacts(Arrays.asList(
-//                    new SimContact(1, null, null, null),
-//                    new SimContact(2, null, null, null),
-//                    new SimContact(3, null, null, null),
-//                    new SimContact(4, "Not null", null, null)
-//            ), mAccount);
-//
-//            final Cursor contactsCursor = queryAllRawContactsInAccount();
-//            assertThat(contactsCursor, hasCount(1));
-//            contactsCursor.close();
-//
-//            final Cursor dataCursor = queryAllDataInAccount();
-//            assertThat(dataCursor, hasCount(1));
-//
-//            dataCursor.close();
-//        }
-//
-//        private Cursor queryAllRawContactsInAccount() {
-//            return new StringableCursor(mResolver.query(ContactsContract.RawContacts.CONTENT_URI, null,
-//                    ContactsContract.RawContacts.ACCOUNT_NAME + "=? AND " +
-//                            ContactsContract.RawContacts.ACCOUNT_TYPE+ "=?",
-//                    new String[] {
-//                            mAccount.name,
-//                            mAccount.type
-//                    }, null));
-//        }
-//
-//        private Cursor queryAllDataInAccount() {
-//            return new StringableCursor(mResolver.query(ContactsContract.Data.CONTENT_URI, null,
-//                    ContactsContract.RawContacts.ACCOUNT_NAME + "=? AND " +
-//                            ContactsContract.RawContacts.ACCOUNT_TYPE+ "=?",
-//                    new String[] {
-//                            mAccount.name,
-//                            mAccount.type
-//                    }, null));
-//        }
-//
-//        private Cursor queryContactWithName(String name) {
-//            return new StringableCursor(mResolver.query(ContactsContract.Data.CONTENT_URI, null,
-//                    ContactsContract.RawContacts.ACCOUNT_NAME + "=? AND " +
-//                            ContactsContract.RawContacts.ACCOUNT_TYPE+ "=? AND " +
-//                            ContactsContract.Data.DISPLAY_NAME + "=?",
-//                    new String[] {
-//                            mAccount.name,
-//                            mAccount.type,
-//                            name
-//                    }, null));
-//        }
-//    }
-//
-//    @LargeTest
-//    // suppressed because failed assumptions are reported as test failures by the build server
-//    @Suppress
-//    @RunWith(AndroidJUnit4.class)
-//    public static class ReadIntegrationTest {
-//        private SimContactsTestHelper mSimTestHelper;
-//        private ArrayList<ContentProviderOperation> mSimSnapshot;
-//
-//        @Before
-//        public void setUp() throws Exception {
-//            mSimTestHelper = new SimContactsTestHelper();
-//
-//            mSimTestHelper.assumeSimWritable();
-//            if (!mSimTestHelper.isSimWritable()) return;
-//
-//            mSimSnapshot = mSimTestHelper.captureRestoreSnapshot();
-//            mSimTestHelper.deleteAllSimContacts();
-//        }
-//
-//        @After
-//        public void tearDown() throws Exception {
-//            mSimTestHelper.restore(mSimSnapshot);
-//        }
-//
-//        @Test
-//        public void readFromSim() {
-//            mSimTestHelper.addSimContact("Test Simone", "15095550101");
-//            mSimTestHelper.addSimContact("Test Simtwo", "15095550102");
-//            mSimTestHelper.addSimContact("Test Simthree", "15095550103");
-//
-//            final SimContactDao sut = new SimContactDao(getContext());
-//            final ArrayList<SimContact> contacts = sut.loadSimContacts();
-//
-//            assertThat(contacts.get(0), isSimContactWithNameAndPhone("Test Simone", "15095550101"));
-//            assertThat(contacts.get(1), isSimContactWithNameAndPhone("Test Simtwo", "15095550102"));
-//            assertThat(contacts.get(2), isSimContactWithNameAndPhone("Test Simthree", "15095550103"));
-//        }
-//    }
-//
-//    private static Matcher<SimContact> isSimContactWithNameAndPhone(final String name,
-//            final String phone) {
-//        return new BaseMatcher<SimContact>() {
-//            @Override
-//            public boolean matches(Object o) {
-//                if (!(o instanceof SimContact))  return false;
-//
-//                SimContact other = (SimContact) o;
-//
-//                return name.equals(other.getName())
-//                        && phone.equals(other.getPhone());
-//            }
-//
-//            @Override
-//            public void describeTo(Description description) {
-//                description.appendText("SimContact with name=" + name + " and phone=" +
-//                        phone);
-//            }
-//        };
-//    }
-//
-//    private static Matcher<Cursor> hasCount(final int count) {
-//        return new BaseMatcher<Cursor>() {
-//            @Override
-//            public boolean matches(Object o) {
-//                if (!(o instanceof Cursor)) return false;
-//                return ((Cursor)o).getCount() == count;
-//            }
-//
-//            @Override
-//            public void describeTo(Description description) {
-//                description.appendText("Cursor with " + count + " rows");
-//            }
-//        };
-//    }
-//
-//    private static Matcher<Cursor> hasMimeType(String type) {
-//        return hasValueForColumn(ContactsContract.Data.MIMETYPE, type);
-//    }
-//
-//    private static Matcher<Cursor> hasValueForColumn(final String column, final String value) {
-//        return new BaseMatcher<Cursor>() {
-//
-//            @Override
-//            public boolean matches(Object o) {
-//                if (!(o instanceof Cursor)) return false;
-//                final Cursor cursor = (Cursor)o;
-//
-//                final int index = cursor.getColumnIndexOrThrow(column);
-//                return value.equals(cursor.getString(index));
-//            }
-//
-//            @Override
-//            public void describeTo(Description description) {
-//                description.appendText("Cursor with " + column + "=" + value);
-//            }
-//        };
-//    }
-//
-//    private static Matcher<Cursor> hasRowMatching(final Matcher<Cursor> rowMatcher) {
-//        return new BaseMatcher<Cursor>() {
-//            @Override
-//            public boolean matches(Object o) {
-//                if (!(o instanceof Cursor)) return false;
-//                final Cursor cursor = (Cursor)o;
-//
-//                cursor.moveToPosition(-1);
-//                while (cursor.moveToNext()) {
-//                    if (rowMatcher.matches(cursor)) return true;
-//                }
-//
-//                return false;
-//            }
-//
-//            @Override
-//            public void describeTo(Description description) {
-//                description.appendText("Cursor with row matching ");
-//                rowMatcher.describeTo(description);
-//            }
-//        };
-//    }
-//
-//    private static Matcher<Cursor> hasName(final String name) {
-//        return hasRowMatching(allOf(
-//                hasMimeType(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE),
-//                hasValueForColumn(
-//                        ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name)));
-//    }
-//
-//    private static Matcher<Cursor> hasPhone(final String phone) {
-//        return hasRowMatching(allOf(
-//                hasMimeType(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE),
-//                hasValueForColumn(
-//                        ContactsContract.CommonDataKinds.Phone.NUMBER, phone)));
-//    }
-//
-//    private static Matcher<Cursor> hasEmail(final String email) {
-//        return hasRowMatching(allOf(
-//                hasMimeType(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE),
-//                hasValueForColumn(
-//                        ContactsContract.CommonDataKinds.Email.ADDRESS, email)));
-//    }
-//
-//    static class StringableCursor extends CursorWrapper {
-//        public StringableCursor(Cursor cursor) {
-//            super(cursor);
-//        }
-//
-//        @Override
-//        public String toString() {
-//            final Cursor wrapped = getWrappedCursor();
-//
-//            if (wrapped.getCount() == 0) {
-//                return "Empty Cursor";
-//            }
-//
-//            return DatabaseUtils.dumpCursorToString(wrapped);
-//        }
-//    }
-//
-//    static Context getContext() {
-//        return InstrumentationRegistry.getTargetContext();
-//    }
-//}
+/*
+ * Copyright (C) 2016 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.contacts.common.database;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.CursorWrapper;
+import android.database.DatabaseUtils;
+import android.provider.ContactsContract;
+import android.support.annotation.RequiresApi;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.filters.SdkSuppress;
+import android.support.test.filters.Suppress;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.contacts.common.model.SimContact;
+import com.android.contacts.common.model.account.AccountWithDataSet;
+import com.android.contacts.tests.AccountsTestHelper;
+import com.android.contacts.tests.SimContactsTestHelper;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.experimental.runners.Enclosed;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import static android.os.Build.VERSION_CODES;
+import static org.hamcrest.Matchers.allOf;
+import static org.junit.Assert.assertThat;
+
+@RunWith(Enclosed.class)
+public class SimContactDaoTests {
+
+    // Lollipop MR1 required for AccountManager.removeAccountExplicitly
+    @RequiresApi(api = VERSION_CODES.LOLLIPOP_MR1)
+    @SdkSuppress(minSdkVersion = VERSION_CODES.LOLLIPOP_MR1)
+    @LargeTest
+    @RunWith(AndroidJUnit4.class)
+    public static class ImportIntegrationTest {
+        private AccountWithDataSet mAccount;
+        private AccountsTestHelper mAccountsHelper;
+        private ContentResolver mResolver;
+
+        @Before
+        public void setUp() throws Exception {
+            mAccountsHelper = new AccountsTestHelper();
+            mAccount = mAccountsHelper.addTestAccount();
+            mResolver = getContext().getContentResolver();
+        }
+
+        @After
+        public void tearDown() throws Exception {
+            mAccountsHelper.cleanup();
+        }
+
+        @Test
+        public void importFromSim() throws Exception {
+            final SimContactDao sut = SimContactDao.create(getContext());
+
+            sut.importContacts(Arrays.asList(
+                    new SimContact(1, "Test One", "15095550101", null),
+                    new SimContact(2, "Test Two", "15095550102", null),
+                    new SimContact(3, "Test Three", "15095550103", new String[] {
+                            "user@example.com", "user2@example.com"
+                    })
+            ), mAccount);
+
+            Cursor cursor = queryContactWithName("Test One");
+            assertThat(cursor, hasCount(2));
+            assertThat(cursor, hasName("Test One"));
+            assertThat(cursor, hasPhone("15095550101"));
+            cursor.close();
+
+            cursor = queryContactWithName("Test Two");
+            assertThat(cursor, hasCount(2));
+            assertThat(cursor, hasName("Test Two"));
+            assertThat(cursor, hasPhone("15095550102"));
+            cursor.close();
+
+            cursor = queryContactWithName("Test Three");
+            assertThat(cursor, hasCount(4));
+            assertThat(cursor, hasName("Test Three"));
+            assertThat(cursor, hasPhone("15095550103"));
+            assertThat(cursor, allOf(hasEmail("user@example.com"), hasEmail("user2@example.com")));
+            cursor.close();
+        }
+
+        @Test
+        public void importContactWhichOnlyHasName() throws Exception {
+            final SimContactDao sut = SimContactDao.create(getContext());
+
+            sut.importContacts(Arrays.asList(
+                    new SimContact(1, "Test importJustName", null, null)
+            ), mAccount);
+
+            Cursor cursor = queryAllDataInAccount();
+
+            assertThat(cursor, hasCount(1));
+            assertThat(cursor, hasName("Test importJustName"));
+            cursor.close();
+        }
+
+        @Test
+        public void importContactWhichOnlyHasPhone() throws Exception {
+            final SimContactDao sut = SimContactDao.create(getContext());
+
+            sut.importContacts(Arrays.asList(
+                    new SimContact(1, null, "15095550111", null)
+            ), mAccount);
+
+            Cursor cursor = queryAllDataInAccount();
+
+            assertThat(cursor, hasCount(1));
+            assertThat(cursor, hasPhone("15095550111"));
+            cursor.close();
+        }
+
+        @Test
+        public void ignoresEmptyContacts() throws Exception {
+            final SimContactDao sut = SimContactDao.create(getContext());
+
+            // This probably isn't possible but we'll test it to demonstrate expected behavior and
+            // just in case it does occur
+            sut.importContacts(Arrays.asList(
+                    new SimContact(1, null, null, null),
+                    new SimContact(2, null, null, null),
+                    new SimContact(3, null, null, null),
+                    new SimContact(4, "Not null", null, null)
+            ), mAccount);
+
+            final Cursor contactsCursor = queryAllRawContactsInAccount();
+            assertThat(contactsCursor, hasCount(1));
+            contactsCursor.close();
+
+            final Cursor dataCursor = queryAllDataInAccount();
+            assertThat(dataCursor, hasCount(1));
+
+            dataCursor.close();
+        }
+
+        private Cursor queryAllRawContactsInAccount() {
+            return new StringableCursor(mResolver.query(ContactsContract.RawContacts.CONTENT_URI,
+                    null, ContactsContract.RawContacts.ACCOUNT_NAME + "=? AND " +
+                            ContactsContract.RawContacts.ACCOUNT_TYPE+ "=?",
+                    new String[] {
+                            mAccount.name,
+                            mAccount.type
+                    }, null));
+        }
+
+        private Cursor queryAllDataInAccount() {
+            return new StringableCursor(mResolver.query(ContactsContract.Data.CONTENT_URI, null,
+                    ContactsContract.RawContacts.ACCOUNT_NAME + "=? AND " +
+                            ContactsContract.RawContacts.ACCOUNT_TYPE+ "=?",
+                    new String[] {
+                            mAccount.name,
+                            mAccount.type
+                    }, null));
+        }
+
+        private Cursor queryContactWithName(String name) {
+            return new StringableCursor(mResolver.query(ContactsContract.Data.CONTENT_URI, null,
+                    ContactsContract.RawContacts.ACCOUNT_NAME + "=? AND " +
+                            ContactsContract.RawContacts.ACCOUNT_TYPE+ "=? AND " +
+                            ContactsContract.Data.DISPLAY_NAME + "=?",
+                    new String[] {
+                            mAccount.name,
+                            mAccount.type,
+                            name
+                    }, null));
+        }
+    }
+
+    @LargeTest
+    // suppressed because failed assumptions are reported as test failures by the build server
+    @Suppress
+    @RunWith(AndroidJUnit4.class)
+    public static class ReadIntegrationTest {
+        private SimContactsTestHelper mSimTestHelper;
+        private ArrayList<ContentProviderOperation> mSimSnapshot;
+
+        @Before
+        public void setUp() throws Exception {
+            mSimTestHelper = new SimContactsTestHelper();
+
+            mSimTestHelper.assumeSimWritable();
+            if (!mSimTestHelper.isSimWritable()) return;
+
+            mSimSnapshot = mSimTestHelper.captureRestoreSnapshot();
+            mSimTestHelper.deleteAllSimContacts();
+        }
+
+        @After
+        public void tearDown() throws Exception {
+            mSimTestHelper.restore(mSimSnapshot);
+        }
+
+        @Test
+        public void readFromSim() {
+            mSimTestHelper.addSimContact("Test Simone", "15095550101");
+            mSimTestHelper.addSimContact("Test Simtwo", "15095550102");
+            mSimTestHelper.addSimContact("Test Simthree", "15095550103");
+
+            final SimContactDao sut = SimContactDao.create(getContext());
+            final ArrayList<SimContact> contacts = sut.loadSimContacts();
+
+            assertThat(contacts.get(0), isSimContactWithNameAndPhone("Test Simone", "15095550101"));
+            assertThat(contacts.get(1), isSimContactWithNameAndPhone("Test Simtwo", "15095550102"));
+            assertThat(contacts.get(2),
+                    isSimContactWithNameAndPhone("Test Simthree", "15095550103"));
+        }
+    }
+
+    private static Matcher<SimContact> isSimContactWithNameAndPhone(final String name,
+            final String phone) {
+        return new BaseMatcher<SimContact>() {
+            @Override
+            public boolean matches(Object o) {
+                if (!(o instanceof SimContact))  return false;
+
+                SimContact other = (SimContact) o;
+
+                return name.equals(other.getName())
+                        && phone.equals(other.getPhone());
+            }
+
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("SimContact with name=" + name + " and phone=" +
+                        phone);
+            }
+        };
+    }
+
+    private static Matcher<Cursor> hasCount(final int count) {
+        return new BaseMatcher<Cursor>() {
+            @Override
+            public boolean matches(Object o) {
+                if (!(o instanceof Cursor)) return false;
+                return ((Cursor)o).getCount() == count;
+            }
+
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("Cursor with " + count + " rows");
+            }
+        };
+    }
+
+    private static Matcher<Cursor> hasMimeType(String type) {
+        return hasValueForColumn(ContactsContract.Data.MIMETYPE, type);
+    }
+
+    private static Matcher<Cursor> hasValueForColumn(final String column, final String value) {
+        return new BaseMatcher<Cursor>() {
+
+            @Override
+            public boolean matches(Object o) {
+                if (!(o instanceof Cursor)) return false;
+                final Cursor cursor = (Cursor)o;
+
+                final int index = cursor.getColumnIndexOrThrow(column);
+                return value.equals(cursor.getString(index));
+            }
+
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("Cursor with " + column + "=" + value);
+            }
+        };
+    }
+
+    private static Matcher<Cursor> hasRowMatching(final Matcher<Cursor> rowMatcher) {
+        return new BaseMatcher<Cursor>() {
+            @Override
+            public boolean matches(Object o) {
+                if (!(o instanceof Cursor)) return false;
+                final Cursor cursor = (Cursor)o;
+
+                cursor.moveToPosition(-1);
+                while (cursor.moveToNext()) {
+                    if (rowMatcher.matches(cursor)) return true;
+                }
+
+                return false;
+            }
+
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("Cursor with row matching ");
+                rowMatcher.describeTo(description);
+            }
+        };
+    }
+
+    private static Matcher<Cursor> hasName(final String name) {
+        return hasRowMatching(allOf(
+                hasMimeType(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE),
+                hasValueForColumn(
+                        ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name)));
+    }
+
+    private static Matcher<Cursor> hasPhone(final String phone) {
+        return hasRowMatching(allOf(
+                hasMimeType(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE),
+                hasValueForColumn(
+                        ContactsContract.CommonDataKinds.Phone.NUMBER, phone)));
+    }
+
+    private static Matcher<Cursor> hasEmail(final String email) {
+        return hasRowMatching(allOf(
+                hasMimeType(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE),
+                hasValueForColumn(
+                        ContactsContract.CommonDataKinds.Email.ADDRESS, email)));
+    }
+
+    static class StringableCursor extends CursorWrapper {
+        public StringableCursor(Cursor cursor) {
+            super(cursor);
+        }
+
+        @Override
+        public String toString() {
+            final Cursor wrapped = getWrappedCursor();
+
+            if (wrapped.getCount() == 0) {
+                return "Empty Cursor";
+            }
+
+            return DatabaseUtils.dumpCursorToString(wrapped);
+        }
+    }
+
+    static Context getContext() {
+        return InstrumentationRegistry.getTargetContext();
+    }
+}
diff --git a/tests/src/com/android/contacts/common/model/DeviceLocalAccountLocatorTests.java b/tests/src/com/android/contacts/common/model/Cp2DeviceLocalAccountLocatorTests.java
similarity index 94%
rename from tests/src/com/android/contacts/common/model/DeviceLocalAccountLocatorTests.java
rename to tests/src/com/android/contacts/common/model/Cp2DeviceLocalAccountLocatorTests.java
index e8c4e2f..192eb44 100644
--- a/tests/src/com/android/contacts/common/model/DeviceLocalAccountLocatorTests.java
+++ b/tests/src/com/android/contacts/common/model/Cp2DeviceLocalAccountLocatorTests.java
@@ -40,12 +40,12 @@
 import java.util.Map;
 
 @SmallTest
-public class DeviceLocalAccountLocatorTests extends AndroidTestCase {
+public class Cp2DeviceLocalAccountLocatorTests extends AndroidTestCase {
 
     // Basic smoke test that just checks that it doesn't throw when loading from CP2. We don't
     // care what CP2 actually contains for this.
     public void testShouldNotCrash() {
-        final DeviceLocalAccountLocator sut = new DeviceLocalAccountLocator(
+        final DeviceLocalAccountLocator sut = new Cp2DeviceLocalAccountLocator(
                 getContext().getContentResolver(),
                 new DeviceLocalAccountTypeFactory.Default(getContext()),
                 Collections.<AccountWithDataSet>emptyList());
@@ -80,7 +80,7 @@
         final DeviceLocalAccountTypeFactory stubFactory = new FakeDeviceAccountTypeFactory()
                 .withDeviceTypes(null, "vnd.sec.contact.phone")
                 .withSimTypes("vnd.sec.contact.sim");
-        final DeviceLocalAccountLocator sut = new DeviceLocalAccountLocator(
+        final DeviceLocalAccountLocator sut = new Cp2DeviceLocalAccountLocator(
                 createStubResolverWithContentQueryResult(queryResult(
                         "user", "com.example",
                         "user", "com.example",
@@ -98,7 +98,7 @@
     }
 
     public void test_getDeviceLocalAccounts_doesNotContainItemsForKnownAccounts() {
-        final DeviceLocalAccountLocator sut = new DeviceLocalAccountLocator(
+        final Cp2DeviceLocalAccountLocator sut = new Cp2DeviceLocalAccountLocator(
                 getContext().getContentResolver(), new FakeDeviceAccountTypeFactory(),
                 Arrays.asList(new AccountWithDataSet("user", "com.example", null),
                         new AccountWithDataSet("user1", "com.example", null),
@@ -116,7 +116,7 @@
         final DeviceLocalAccountTypeFactory stubFactory = new FakeDeviceAccountTypeFactory()
                 .withDeviceTypes(null, "vnd.sec.contact.phone")
                 .withSimTypes("vnd.sec.contact.sim");
-        final DeviceLocalAccountLocator sut = new DeviceLocalAccountLocator(
+        final DeviceLocalAccountLocator sut = new Cp2DeviceLocalAccountLocator(
                 createContentResolverWithProvider(new FakeContactsProvider()
                         .withQueryResult(ContactsContract.Settings.CONTENT_URI, queryResult(
                                 "phone_account", "vnd.sec.contact.phone",
@@ -130,7 +130,7 @@
         final DeviceLocalAccountTypeFactory stubFactory = new FakeDeviceAccountTypeFactory()
                 .withDeviceTypes(null, "vnd.sec.contact.phone")
                 .withSimTypes("vnd.sec.contact.sim");
-        final DeviceLocalAccountLocator sut = new DeviceLocalAccountLocator(
+        final DeviceLocalAccountLocator sut = new Cp2DeviceLocalAccountLocator(
                 createContentResolverWithProvider(new FakeContactsProvider()
                         .withQueryResult(ContactsContract.Groups.CONTENT_URI, queryResult(
                                 "phone_account", "vnd.sec.contact.phone",
@@ -142,7 +142,7 @@
 
     private DeviceLocalAccountLocator createWithQueryResult(
             Cursor cursor) {
-        final DeviceLocalAccountLocator locator = new DeviceLocalAccountLocator(
+        final DeviceLocalAccountLocator locator = new Cp2DeviceLocalAccountLocator(
                 createStubResolverWithContentQueryResult(cursor),
                 new DeviceLocalAccountTypeFactory.Default(getContext()),
                 Collections.<AccountWithDataSet>emptyList());
diff --git a/tests/src/com/android/contacts/common/model/SimContactTests.java b/tests/src/com/android/contacts/common/model/SimContactTests.java
index c37c270..359379e 100644
--- a/tests/src/com/android/contacts/common/model/SimContactTests.java
+++ b/tests/src/com/android/contacts/common/model/SimContactTests.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright (C) 2016 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.contacts.common.model;
 
 import android.os.Parcel;
@@ -10,10 +25,6 @@
 import static org.hamcrest.Matchers.equalTo;
 import static org.junit.Assert.assertThat;
 
-/**
- * Created by mhagerott on 10/6/16.
- */
-
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class SimContactTests {
@@ -21,7 +32,7 @@
     public void parcelRoundtrip() {
         assertParcelsCorrectly(new SimContact(1, "name1", "phone1",
                 new String[] { "email1a", "email1b" }));
-        assertParcelsCorrectly(new SimContact(2, "name2", "phone2", null, 2));
+        assertParcelsCorrectly(new SimContact(2, "name2", "phone2", null));
         assertParcelsCorrectly(new SimContact(3, "name3", null,
                 new String[] { "email3" }));
         assertParcelsCorrectly(new SimContact(4, null, "phone4",
diff --git a/tests/src/com/android/contacts/tests/SimContactsTestHelper.java b/tests/src/com/android/contacts/tests/SimContactsTestHelper.java
index 337ea62..c2ffead 100644
--- a/tests/src/com/android/contacts/tests/SimContactsTestHelper.java
+++ b/tests/src/com/android/contacts/tests/SimContactsTestHelper.java
@@ -1,198 +1,198 @@
-///*
-// * Copyright (C) 2016 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.contacts.tests;
-//
-//import android.content.ContentProvider;
-//import android.content.ContentProviderOperation;
-//import android.content.ContentProviderResult;
-//import android.content.ContentResolver;
-//import android.content.ContentValues;
-//import android.content.Context;
-//import android.content.OperationApplicationException;
-//import android.database.Cursor;
-//import android.net.Uri;
-//import android.os.RemoteException;
-//import android.support.annotation.NonNull;
-//import android.support.test.InstrumentationRegistry;
-//import android.telephony.TelephonyManager;
-//
-//import com.android.contacts.common.model.SimContact;
-//import com.android.contacts.common.database.SimContactDao;
-//import com.android.contacts.common.test.mocks.MockContentProvider;
-//
-//import java.util.ArrayList;
-//import java.util.List;
-//
-//import static org.hamcrest.Matchers.equalTo;
-//import static org.junit.Assume.assumeThat;
-//import static org.junit.Assume.assumeTrue;
-//
-//public class SimContactsTestHelper {
-//
-//    private final Context mContext;
-//    private final TelephonyManager mTelephonyManager;
-//    private final ContentResolver mResolver;
-//    private final SimContactDao mSimDao;
-//
-//    public SimContactsTestHelper() {
-//        this(InstrumentationRegistry.getTargetContext());
-//    }
-//
-//    public SimContactsTestHelper(Context context) {
-//        mContext = context;
-//        mResolver = context.getContentResolver();
-//        mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
-//        mSimDao = new SimContactDao(context);
-//    }
-//
-//    public int getSimContactCount() {
-//        Cursor cursor = mContext.getContentResolver().query(SimContactDao.ICC_CONTENT_URI,
-//                null, null, null, null);
-//        try {
-//            return cursor.getCount();
-//        } finally {
-//            cursor.close();
-//        }
-//    }
-//
-//    public ContentValues iccRow(long id, String name, String number, String emails) {
-//        ContentValues values = new ContentValues();
-//        values.put(SimContactDao._ID, id);
-//        values.put(SimContactDao.NAME, name);
-//        values.put(SimContactDao.NUMBER, number);
-//        values.put(SimContactDao.EMAILS, emails);
-//        return values;
-//    }
-//
-//    public ContentProvider iccProviderExpectingNoQueries() {
-//        return new MockContentProvider();
-//    }
-//
-//    public ContentProvider emptyIccProvider() {
-//        final MockContentProvider provider = new MockContentProvider();
-//        provider.expectQuery(SimContactDao.ICC_CONTENT_URI)
-//                .withDefaultProjection(
-//                        SimContactDao._ID, SimContactDao.NAME,
-//                        SimContactDao.NUMBER, SimContactDao.EMAILS)
-//                .withAnyProjection()
-//                .withAnySelection()
-//                .withAnySortOrder()
-//                .returnEmptyCursor();
-//        return provider;
-//    }
-//
-//    public Uri addSimContact(String name, String number) {
-//        ContentValues values = new ContentValues();
-//        // Oddly even though it's called name when querying we have to use "tag" for it to work
-//        // when inserting.
-//        if (name != null) {
-//            values.put("tag", name);
-//        }
-//        if (number != null) {
-//            values.put(SimContactDao.NUMBER, number);
-//        }
-//        return mResolver.insert(SimContactDao.ICC_CONTENT_URI, values);
-//    }
-//
-//    public ContentProviderResult[] deleteAllSimContacts()
-//            throws RemoteException, OperationApplicationException {
-//        SimContactDao dao = new SimContactDao(mContext);
-//        List<SimContact> contacts = dao.loadSimContacts();
-//        ArrayList<ContentProviderOperation> ops = new ArrayList<>();
-//        for (SimContact contact : contacts) {
-//            ops.add(ContentProviderOperation
-//                    .newDelete(SimContactDao.ICC_CONTENT_URI)
-//                    .withSelection(getWriteSelection(contact), null)
-//                    .build());
-//        }
-//        return mResolver.applyBatch(SimContactDao.ICC_CONTENT_URI.getAuthority(), ops);
-//    }
-//
-//    public ContentProviderResult[] restore(ArrayList<ContentProviderOperation> restoreOps)
-//            throws RemoteException, OperationApplicationException {
-//        if (restoreOps == null) return null;
-//
-//        // Remove SIM contacts because we assume that caller wants the data to be in the exact
-//        // state as when the restore ops were captured.
-//        deleteAllSimContacts();
-//        return mResolver.applyBatch(SimContactDao.ICC_CONTENT_URI.getAuthority(), restoreOps);
-//    }
-//
-//    public ArrayList<ContentProviderOperation> captureRestoreSnapshot() {
-//        ArrayList<SimContact> contacts = mSimDao.loadSimContacts();
-//
-//        ArrayList<ContentProviderOperation> ops = new ArrayList<>();
-//        for (SimContact contact : contacts) {
-//            final String[] emails = contact.getEmails();
-//            if (emails != null && emails.length > 0) {
-//                throw new IllegalStateException("Cannot restore emails." +
-//                        " Please manually remove SIM contacts with emails.");
-//            }
-//            ops.add(ContentProviderOperation
-//                    .newInsert(SimContactDao.ICC_CONTENT_URI)
-//                    .withValue("tag", contact.getName())
-//                    .withValue("number", contact.getPhone())
-//                    .build());
-//        }
-//        return ops;
-//    }
-//
-//    public String getWriteSelection(SimContact simContact) {
-//        return "tag='" + simContact.getName() + "' AND " + SimContactDao.NUMBER + "='" +
-//                simContact.getPhone() + "'";
-//    }
-//
-//    public int deleteSimContact(@NonNull  String name, @NonNull  String number) {
-//        // IccProvider doesn't use the selection args.
-//        final String selection = "tag='" + name + "' AND " +
-//                SimContactDao.NUMBER + "='" + number + "'";
-//        return mResolver.delete(SimContactDao.ICC_CONTENT_URI, selection, null);
-//    }
-//
-//    public boolean isSimReady() {
-//        return mTelephonyManager.getSimState() == TelephonyManager.SIM_STATE_READY;
-//    }
-//
-//    public boolean doesSimHaveContacts() {
-//        return isSimReady() && getSimContactCount() > 0;
-//    }
-//
-//    public boolean isSimWritable() {
-//        if (!isSimReady()) return false;
-//        final String name = "writabeProbe" + System.nanoTime();
-//        final Uri uri = addSimContact(name, "15095550101");
-//        return uri != null && deleteSimContact(name, "15095550101") == 1;
-//    }
-//
-//    public void assumeSimReady() {
-//        assumeTrue(isSimReady());
-//    }
-//
-//    public void assumeHasSimContacts() {
-//        assumeTrue(doesSimHaveContacts());
-//    }
-//
-//    public void assumeSimCardAbsent() {
-//        assumeThat(mTelephonyManager.getSimState(), equalTo(TelephonyManager.SIM_STATE_ABSENT));
-//    }
-//
-//    // The emulator reports SIM_STATE_READY but writes are ignored. This verifies that the
-//    // device will actually persist writes to the SIM card.
-//    public void assumeSimWritable() {
-//        assumeSimReady();
-//        assumeTrue(isSimWritable());
-//    }
-//}
+/*
+ * Copyright (C) 2016 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.contacts.tests;
+
+import android.content.ContentProvider;
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.OperationApplicationException;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.support.annotation.NonNull;
+import android.support.test.InstrumentationRegistry;
+import android.telephony.TelephonyManager;
+
+import com.android.contacts.common.model.SimContact;
+import com.android.contacts.common.database.SimContactDao;
+import com.android.contacts.common.test.mocks.MockContentProvider;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assume.assumeThat;
+import static org.junit.Assume.assumeTrue;
+
+public class SimContactsTestHelper {
+
+    private final Context mContext;
+    private final TelephonyManager mTelephonyManager;
+    private final ContentResolver mResolver;
+    private final SimContactDao mSimDao;
+
+    public SimContactsTestHelper() {
+        this(InstrumentationRegistry.getTargetContext());
+    }
+
+    public SimContactsTestHelper(Context context) {
+        mContext = context;
+        mResolver = context.getContentResolver();
+        mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+        mSimDao = SimContactDao.create(context);
+    }
+
+    public int getSimContactCount() {
+        Cursor cursor = mContext.getContentResolver().query(SimContactDao.ICC_CONTENT_URI,
+                null, null, null, null);
+        try {
+            return cursor.getCount();
+        } finally {
+            cursor.close();
+        }
+    }
+
+    public ContentValues iccRow(long id, String name, String number, String emails) {
+        ContentValues values = new ContentValues();
+        values.put(SimContactDao._ID, id);
+        values.put(SimContactDao.NAME, name);
+        values.put(SimContactDao.NUMBER, number);
+        values.put(SimContactDao.EMAILS, emails);
+        return values;
+    }
+
+    public ContentProvider iccProviderExpectingNoQueries() {
+        return new MockContentProvider();
+    }
+
+    public ContentProvider emptyIccProvider() {
+        final MockContentProvider provider = new MockContentProvider();
+        provider.expectQuery(SimContactDao.ICC_CONTENT_URI)
+                .withDefaultProjection(
+                        SimContactDao._ID, SimContactDao.NAME,
+                        SimContactDao.NUMBER, SimContactDao.EMAILS)
+                .withAnyProjection()
+                .withAnySelection()
+                .withAnySortOrder()
+                .returnEmptyCursor();
+        return provider;
+    }
+
+    public Uri addSimContact(String name, String number) {
+        ContentValues values = new ContentValues();
+        // Oddly even though it's called name when querying we have to use "tag" for it to work
+        // when inserting.
+        if (name != null) {
+            values.put("tag", name);
+        }
+        if (number != null) {
+            values.put(SimContactDao.NUMBER, number);
+        }
+        return mResolver.insert(SimContactDao.ICC_CONTENT_URI, values);
+    }
+
+    public ContentProviderResult[] deleteAllSimContacts()
+            throws RemoteException, OperationApplicationException {
+        SimContactDao dao = SimContactDao.create(mContext);
+        List<SimContact> contacts = dao.loadSimContacts();
+        ArrayList<ContentProviderOperation> ops = new ArrayList<>();
+        for (SimContact contact : contacts) {
+            ops.add(ContentProviderOperation
+                    .newDelete(SimContactDao.ICC_CONTENT_URI)
+                    .withSelection(getWriteSelection(contact), null)
+                    .build());
+        }
+        return mResolver.applyBatch(SimContactDao.ICC_CONTENT_URI.getAuthority(), ops);
+    }
+
+    public ContentProviderResult[] restore(ArrayList<ContentProviderOperation> restoreOps)
+            throws RemoteException, OperationApplicationException {
+        if (restoreOps == null) return null;
+
+        // Remove SIM contacts because we assume that caller wants the data to be in the exact
+        // state as when the restore ops were captured.
+        deleteAllSimContacts();
+        return mResolver.applyBatch(SimContactDao.ICC_CONTENT_URI.getAuthority(), restoreOps);
+    }
+
+    public ArrayList<ContentProviderOperation> captureRestoreSnapshot() {
+        ArrayList<SimContact> contacts = mSimDao.loadSimContacts();
+
+        ArrayList<ContentProviderOperation> ops = new ArrayList<>();
+        for (SimContact contact : contacts) {
+            final String[] emails = contact.getEmails();
+            if (emails != null && emails.length > 0) {
+                throw new IllegalStateException("Cannot restore emails." +
+                        " Please manually remove SIM contacts with emails.");
+            }
+            ops.add(ContentProviderOperation
+                    .newInsert(SimContactDao.ICC_CONTENT_URI)
+                    .withValue("tag", contact.getName())
+                    .withValue("number", contact.getPhone())
+                    .build());
+        }
+        return ops;
+    }
+
+    public String getWriteSelection(SimContact simContact) {
+        return "tag='" + simContact.getName() + "' AND " + SimContactDao.NUMBER + "='" +
+                simContact.getPhone() + "'";
+    }
+
+    public int deleteSimContact(@NonNull  String name, @NonNull  String number) {
+        // IccProvider doesn't use the selection args.
+        final String selection = "tag='" + name + "' AND " +
+                SimContactDao.NUMBER + "='" + number + "'";
+        return mResolver.delete(SimContactDao.ICC_CONTENT_URI, selection, null);
+    }
+
+    public boolean isSimReady() {
+        return mTelephonyManager.getSimState() == TelephonyManager.SIM_STATE_READY;
+    }
+
+    public boolean doesSimHaveContacts() {
+        return isSimReady() && getSimContactCount() > 0;
+    }
+
+    public boolean isSimWritable() {
+        if (!isSimReady()) return false;
+        final String name = "writabeProbe" + System.nanoTime();
+        final Uri uri = addSimContact(name, "15095550101");
+        return uri != null && deleteSimContact(name, "15095550101") == 1;
+    }
+
+    public void assumeSimReady() {
+        assumeTrue(isSimReady());
+    }
+
+    public void assumeHasSimContacts() {
+        assumeTrue(doesSimHaveContacts());
+    }
+
+    public void assumeSimCardAbsent() {
+        assumeThat(mTelephonyManager.getSimState(), equalTo(TelephonyManager.SIM_STATE_ABSENT));
+    }
+
+    // The emulator reports SIM_STATE_READY but writes are ignored. This verifies that the
+    // device will actually persist writes to the SIM card.
+    public void assumeSimWritable() {
+        assumeSimReady();
+        assumeTrue(isSimWritable());
+    }
+}