Add device and SIM to AccountTypeManager

This makes these account types available throughout the app rather than
just the Nav drawer.

Test:
Added unit tests for new classes; run with:
$ adb shell am instrument -w \
  com.google.android.tests/android.test.InstrumentationTestRunner

Manually on Nexus 6, LG G5 and Samsung S7 with device and SIM contacts
in CP2 by verifying "Device" and "SIM" options were available in
* nav drawer
* account list accessed by Pressing FAB without default account set
* editor account dropdown for new contact
* editor account label  when editing existing contact
* picker for default account in settings
* settings customize screen account list

Bug 30867780

Change-Id: I329381ccc58d59f2e27f65a3d9dc0164fb20c971
diff --git a/src/com/android/contacts/ContactsDrawerActivity.java b/src/com/android/contacts/ContactsDrawerActivity.java
index f976807..34c95c6 100644
--- a/src/com/android/contacts/ContactsDrawerActivity.java
+++ b/src/com/android/contacts/ContactsDrawerActivity.java
@@ -54,12 +54,10 @@
 import com.android.contacts.common.list.ContactListFilter;
 import com.android.contacts.common.list.ContactListFilterController;
 import com.android.contacts.common.model.AccountTypeManager;
-import com.android.contacts.common.model.account.AccountType;
 import com.android.contacts.common.model.account.AccountWithDataSet;
 import com.android.contacts.common.preference.ContactsPreferenceActivity;
 import com.android.contacts.common.util.AccountFilterUtil;
 import com.android.contacts.common.util.AccountsListAdapter.AccountListFilter;
-import com.android.contacts.common.util.DeviceAccountPresentationValues;
 import com.android.contacts.common.util.ImplicitIntentsUtil;
 import com.android.contacts.common.util.ViewUtil;
 import com.android.contacts.editor.ContactEditorFragment;
@@ -72,10 +70,11 @@
 import com.android.contacts.interactions.AccountFiltersFragment;
 import com.android.contacts.interactions.AccountFiltersFragment.AccountFiltersListener;
 import com.android.contacts.quickcontact.QuickContactActivity;
+import com.android.contacts.common.model.account.AccountDisplayInfo;
+import com.android.contacts.common.model.account.AccountDisplayInfoFactory;
 import com.android.contacts.util.SharedPreferenceUtil;
 import com.android.contactsbind.Assistants;
 import com.android.contactsbind.HelpUtils;
-import com.android.contactsbind.ObjectFactory;
 
 import java.util.HashMap;
 import java.util.Iterator;
@@ -173,7 +172,6 @@
 
     // The account the new group will be created under.
     private AccountWithDataSet mNewGroupAccount;
-    private DeviceAccountPresentationValues mDeviceAccountPresentationValues;
 
     private int mPositionOfLastGroup;
 
@@ -186,8 +184,6 @@
 
         super.setContentView(R.layout.contacts_drawer_activity);
 
-        mDeviceAccountPresentationValues = ObjectFactory.createDeviceAccountPresentationValues(this);
-
         // Set up the action bar.
         mToolbar = getView(R.id.toolbar);
         setSupportActionBar(mToolbar);
@@ -461,6 +457,9 @@
 
     @Override
     public void onFiltersLoaded(List<ContactListFilter> accountFilterItems) {
+        final AccountDisplayInfoFactory accountDisplayFactory = AccountDisplayInfoFactory.
+                fromListFilters(this, accountFilterItems);
+
         final Menu menu = mNavigationView.getMenu();
         final MenuItem filtersMenuItem = menu.findItem(R.id.nav_filters);
         final SubMenu subMenu = filtersMenuItem.getSubMenu();
@@ -473,12 +472,12 @@
 
         int positionOfLastFilter = mPositionOfLastGroup + GAP_BETWEEN_TWO_MENU_GROUPS;
 
-        mDeviceAccountPresentationValues.setFilters(accountFilterItems);
-
         for (int i = 0; i < accountFilterItems.size(); i++) {
             positionOfLastFilter++;
             final ContactListFilter filter = accountFilterItems.get(i);
-            final CharSequence menuName = mDeviceAccountPresentationValues.getLabel(i);
+            final AccountDisplayInfo displayableAccount =
+                    accountDisplayFactory.getAccountDisplayInfoFor(filter);
+            final CharSequence menuName = displayableAccount.getNameLabel();
             final MenuItem menuItem = subMenu.add(R.id.nav_filters_items, Menu.NONE,
                     positionOfLastFilter, menuName);
             mFilterMenuMap.put(filter, menuItem);
@@ -502,17 +501,15 @@
                     return true;
                 }
             });
-            menuItem.setIcon(mDeviceAccountPresentationValues.getIcon(i));
+            menuItem.setIcon(displayableAccount.getIcon());
             // Get rid of the default menu item overlay and show original account icons.
             menuItem.getIcon().setColorFilter(Color.TRANSPARENT, PorterDuff.Mode.SRC_ATOP);
             // Create a dummy action view to attach extra hidden content description to the menuItem
             // for Talkback. We want Talkback to read out the account type but not have it be part
             // of the menuItem title.
-            final AccountType account = AccountTypeManager.getInstance(this)
-                    .getAccountType(filter.accountType, filter.dataSet);
             LinearLayout view = (LinearLayout) LayoutInflater.from(this)
                     .inflate(R.layout.account_type_info, null);
-            view.setContentDescription(account.getDisplayLabel(this));
+            view.setContentDescription(displayableAccount.getTypeLabel());
             view.setVisibility(View.VISIBLE);
             menuItem.setActionView(view);
         }
diff --git a/src/com/android/contacts/activities/ContactEditorAccountsChangedActivity.java b/src/com/android/contacts/activities/ContactEditorAccountsChangedActivity.java
index c4c0e27..9b211ab 100644
--- a/src/com/android/contacts/activities/ContactEditorAccountsChangedActivity.java
+++ b/src/com/android/contacts/activities/ContactEditorAccountsChangedActivity.java
@@ -106,7 +106,7 @@
                     AccountListFilter.ACCOUNTS_CONTACT_WRITABLE);
             accountListView.setAdapter(mAccountListAdapter);
             accountListView.setOnItemClickListener(mAccountListItemClickListener);
-        } else if (numAccounts == 1) {
+        } else if (numAccounts == 1 && !accounts.get(0).isLocalAccount()) {
             // If the user has 1 writable account we will just show the user a message with 2
             // possible action buttons.
             view = View.inflate(this,
diff --git a/src/com/android/contacts/activities/ContactEditorBaseActivity.java b/src/com/android/contacts/activities/ContactEditorBaseActivity.java
index 8a8ce1d..55fcddb 100644
--- a/src/com/android/contacts/activities/ContactEditorBaseActivity.java
+++ b/src/com/android/contacts/activities/ContactEditorBaseActivity.java
@@ -71,6 +71,16 @@
     // 3 used for ContactDeletionInteraction.RESULT_CODE_DELETED
     public static final int RESULT_CODE_EDITED = 4;
 
+    /**
+     * The contact will be saved to the device local account when this is set for an insert. This
+     * is necessary because {@link android.accounts.Account} cannot be created with null values
+     * for the name and type and an Account is needed for
+     * {@link android.provider.ContactsContract.Intents.Insert#EXTRA_ACCOUNT}
+     */
+    public static final String EXTRA_SAVE_TO_DEVICE_FLAG =
+            "com.android.contacts.SAVE_TO_DEVICE_FLAG";
+
+
     protected int mActionBarTitleResId;
 
     /**
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index 492cd8b..7358fb7 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -37,7 +37,6 @@
 import android.provider.ContactsContract.ProviderStatus;
 import android.provider.ContactsContract.QuickContact;
 import android.support.design.widget.CoordinatorLayout;
-import android.support.design.widget.FloatingActionButton;
 import android.support.design.widget.Snackbar;
 import android.support.v13.app.FragmentPagerAdapter;
 import android.support.v4.content.LocalBroadcastManager;
@@ -59,6 +58,8 @@
 import android.widget.ImageButton;
 import android.widget.Toast;
 
+import com.android.contacts.common.model.account.AccountDisplayInfo;
+import com.android.contacts.common.model.account.AccountDisplayInfoFactory;
 import com.android.contacts.ContactSaveService;
 import com.android.contacts.ContactsDrawerActivity;
 import com.android.contacts.R;
@@ -78,7 +79,6 @@
 import com.android.contacts.common.logging.ScreenEvent.ScreenType;
 import com.android.contacts.common.model.AccountTypeManager;
 import com.android.contacts.common.model.account.AccountWithDataSet;
-import com.android.contacts.common.model.account.GoogleAccountType;
 import com.android.contacts.common.util.Constants;
 import com.android.contacts.common.util.ImplicitIntentsUtil;
 import com.android.contacts.common.widget.FloatingActionButtonController;
@@ -1441,19 +1441,24 @@
 
     public void onFabClicked() {
         final Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
-        final Bundle extras = getIntent().getExtras();
-        if (extras != null) {
-            final ContactListFilter filter = mContactListFilterController.getFilter();
-            // If we are in account view, we pass the account explicitly in order to
-            // create contact in the account. This will prevent the default account dialog
-            // from being displayed.
-            if (!isAllContactsFilter(filter) && !isDeviceContactsFilter(filter)) {
-                final Account account = new Account(filter.accountName, filter.accountType);
-                extras.putParcelable(Intents.Insert.EXTRA_ACCOUNT, account);
-                extras.putString(Intents.Insert.EXTRA_DATA_SET, filter.dataSet);
-            }
-            intent.putExtras(extras);
+        // Copy our extras into the new intent.
+        intent.putExtras(getIntent());
+
+        final ContactListFilter filter = mContactListFilterController.getFilter();
+        // If we are in account view, we pass the account explicitly in order to
+        // create contact in the account. This will prevent the default account dialog
+        // from being displayed.
+        if (!isAllContactsFilter(filter) && filter.accountName != null &&
+                filter.accountType != null) {
+            final Account account = new Account(filter.accountName, filter.accountType);
+            intent.putExtra(Intents.Insert.EXTRA_ACCOUNT, account);
+            intent.putExtra(Intents.Insert.EXTRA_DATA_SET, filter.dataSet);
+        } else if (isDeviceContactsFilter(filter)) {
+            // It's OK to add this even though it's an implicit intent. If a different app
+            // receives the intent it should just ignore the flag.
+            intent.putExtra(CompactContactEditorActivity.EXTRA_SAVE_TO_DEVICE_FLAG, true);
         }
+
         try {
             ImplicitIntentsUtil.startActivityInApp(PeopleActivity.this, intent);
         } catch (ActivityNotFoundException ex) {
@@ -1502,10 +1507,15 @@
     }
 
     private String getActionBarTitleForAccount(ContactListFilter filter) {
-        if (GoogleAccountType.ACCOUNT_TYPE.equals(filter.accountType)) {
+        final AccountDisplayInfoFactory factory =
+                AccountDisplayInfoFactory.forWritableAccounts(this);
+        final AccountDisplayInfo displayableAccount = factory.getAccountDisplayInfoFor(filter);
+        if (displayableAccount.hasGoogleAccountType()) {
             return getString(R.string.title_from_google);
+        } else {
+            return displayableAccount.withFormattedName(this, R.string.title_from_other_accounts)
+                    .getNameLabel().toString();
         }
-        return getString(R.string.title_from_other_accounts, filter.accountName);
     }
 
     // Persist filter only when it's of the type FILTER_TYPE_ALL_ACCOUNTS.
diff --git a/src/com/android/contacts/common/list/ContactListFilter.java b/src/com/android/contacts/common/list/ContactListFilter.java
index e99c374..2bae617 100644
--- a/src/com/android/contacts/common/list/ContactListFilter.java
+++ b/src/com/android/contacts/common/list/ContactListFilter.java
@@ -25,6 +25,7 @@
 import android.text.TextUtils;
 
 import com.android.contacts.common.logging.ListEvent;
+import com.android.contacts.common.model.account.AccountWithDataSet;
 
 /**
  * Contact list filter parameters.
@@ -330,6 +331,17 @@
         return uriBuilder;
     }
 
+    public AccountWithDataSet toAccountWithDataSet() {
+        if (filterType == FILTER_TYPE_ACCOUNT) {
+            return new AccountWithDataSet(accountName, accountType, dataSet);
+        } else if (filterType == FILTER_TYPE_DEVICE_CONTACTS) {
+            return AccountWithDataSet.getLocalAccount();
+        } else {
+            throw new IllegalStateException("Cannot create Account from filter type " +
+                    filterTypeToString(filterType));
+        }
+    }
+
     public String toDebugString() {
         final StringBuilder builder = new StringBuilder();
         builder.append("[filter type: " + filterType + " (" + filterTypeToString(filterType) + ")");
diff --git a/src/com/android/contacts/common/list/CustomContactListFilterActivity.java b/src/com/android/contacts/common/list/CustomContactListFilterActivity.java
index 74e8f84..4de57bb 100644
--- a/src/com/android/contacts/common/list/CustomContactListFilterActivity.java
+++ b/src/com/android/contacts/common/list/CustomContactListFilterActivity.java
@@ -56,6 +56,8 @@
 import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
 import android.widget.TextView;
 
+import com.android.contacts.common.model.account.AccountDisplayInfo;
+import com.android.contacts.common.model.account.AccountDisplayInfoFactory;
 import com.android.contacts.common.R;
 import com.android.contacts.common.model.AccountTypeManager;
 import com.android.contacts.common.model.ValuesDelta;
@@ -71,6 +73,7 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.Iterator;
+import java.util.List;
 
 /**
  * Shows a list of all available {@link Groups} available, letting the user
@@ -139,7 +142,11 @@
             final ContentResolver resolver = context.getContentResolver();
 
             final AccountSet accounts = new AccountSet();
-            for (AccountWithDataSet account : accountTypes.getAccounts(false)) {
+
+            final List<AccountWithDataSet> sourceAccounts = accountTypes.getAccounts(false);
+            final AccountDisplayInfoFactory displayableAccountFactory =
+                    new AccountDisplayInfoFactory(context, sourceAccounts);
+            for (AccountWithDataSet account : sourceAccounts) {
                 final AccountType accountType = accountTypes.getAccountTypeForAccount(account);
                 if (accountType.isExtension() && !account.hasData(context)) {
                     // Extension with no data -- skip.
@@ -147,7 +154,8 @@
                 }
 
                 AccountDisplay accountDisplay =
-                        new AccountDisplay(resolver, account.name, account.type, account.dataSet);
+                        new AccountDisplay(resolver, account.name, account.type, account.dataSet,
+                                displayableAccountFactory.getAccountDisplayInfo(account));
 
                 final Uri.Builder groupsUri = Groups.CONTENT_URI.buildUpon()
                         .appendQueryParameter(Groups.ACCOUNT_NAME, account.name)
@@ -466,20 +474,30 @@
         public final String mName;
         public final String mType;
         public final String mDataSet;
+        public final AccountDisplayInfo mAccountDisplayInfo;
 
         public GroupDelta mUngrouped;
         public ArrayList<GroupDelta> mSyncedGroups = Lists.newArrayList();
         public ArrayList<GroupDelta> mUnsyncedGroups = Lists.newArrayList();
 
+        public GroupDelta getGroup(int position) {
+            if (position < mSyncedGroups.size()) {
+                return mSyncedGroups.get(position);
+            }
+            position -= mSyncedGroups.size();
+            return mUnsyncedGroups.get(position);
+        }
+
         /**
          * Build an {@link AccountDisplay} covering all {@link Groups} under the
          * given {@link AccountWithDataSet}.
          */
         public AccountDisplay(ContentResolver resolver, String accountName, String accountType,
-                String dataSet) {
+                String dataSet, AccountDisplayInfo displayableInfo) {
             mName = accountName;
             mType = accountType;
             mDataSet = dataSet;
+            mAccountDisplayInfo = displayableInfo;
         }
 
         /**
@@ -593,12 +611,11 @@
 
             final AccountDisplay account = (AccountDisplay)this.getGroup(groupPosition);
 
-            final AccountType accountType = mAccountTypes.getAccountType(
-                    account.mType, account.mDataSet);
-
-            text1.setText(account.mName);
-            text1.setVisibility(account.mName == null ? View.GONE : View.VISIBLE);
-            text2.setText(accountType.getDisplayLabel(mContext));
+            text1.setText(account.mAccountDisplayInfo.getNameLabel());
+            text1.setVisibility(!account.mAccountDisplayInfo.isDeviceAccount()
+                    || account.mAccountDisplayInfo.hasDistinctName()
+                    ? View.VISIBLE : View.GONE);
+            text2.setText(account.mAccountDisplayInfo.getTypeLabel());
 
             final int textColor = mContext.getResources().getColor(isExpanded
                     ? R.color.dialtacts_theme_color
@@ -650,9 +667,10 @@
         public Object getChild(int groupPosition, int childPosition) {
             final AccountDisplay account = mAccounts.get(groupPosition);
             final boolean validChild = childPosition >= 0
-                    && childPosition < account.mSyncedGroups.size();
+                    && childPosition < account.mSyncedGroups.size()
+                    + account.mUnsyncedGroups.size();
             if (validChild) {
-                return account.mSyncedGroups.get(childPosition);
+                return account.getGroup(childPosition);
             } else {
                 return null;
             }
@@ -673,8 +691,7 @@
         public int getChildrenCount(int groupPosition) {
             // Count is any synced groups, plus possible footer
             final AccountDisplay account = mAccounts.get(groupPosition);
-            final boolean anyHidden = account.mUnsyncedGroups.size() > 0;
-            return account.mSyncedGroups.size() + (anyHidden ? 1 : 0);
+            return account.mSyncedGroups.size() + account.mUnsyncedGroups.size();
         }
 
         @Override
diff --git a/src/com/android/contacts/common/model/AccountTypeManager.java b/src/com/android/contacts/common/model/AccountTypeManager.java
index 35a7a3a..c9f68d4 100644
--- a/src/com/android/contacts/common/model/AccountTypeManager.java
+++ b/src/com/android/contacts/common/model/AccountTypeManager.java
@@ -29,6 +29,7 @@
 import android.content.SyncStatusObserver;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Handler;
@@ -37,6 +38,7 @@
 import android.os.Message;
 import android.os.SystemClock;
 import android.provider.ContactsContract;
+import android.support.v4.content.ContextCompat;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.TimingLogger;
@@ -53,7 +55,7 @@
 import com.android.contacts.common.model.account.SamsungAccountType;
 import com.android.contacts.common.model.dataitem.DataKind;
 import com.android.contacts.common.util.Constants;
-import com.android.contacts.common.util.DeviceAccountFilter;
+import com.android.contacts.common.util.DeviceLocalAccountTypeFactory;
 import com.android.contactsbind.ObjectFactory;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Objects;
@@ -65,13 +67,14 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.atomic.AtomicBoolean;
 
+import static com.android.contacts.common.util.DeviceLocalAccountTypeFactory.Util.isLocalAccountType;
+
 /**
  * Singleton holder for all parsed {@link AccountType} available on the
  * system, typically filled through {@link PackageManager} queries.
@@ -87,11 +90,16 @@
      * the available authenticators. This method can safely be called from the UI thread.
      */
     public static AccountTypeManager getInstance(Context context) {
+        if (!hasRequiredPermissions(context)) {
+            // Hopefully any component that depends on the values returned by this class
+            // will be restarted if the permissions change.
+            return EMPTY;
+        }
         synchronized (mInitializationLock) {
             if (mAccountTypeManager == null) {
                 context = context.getApplicationContext();
                 mAccountTypeManager = new AccountTypeManagerImpl(context,
-                        ObjectFactory.getDeviceAccountFilter(context));
+                        ObjectFactory.getDeviceLocalAccountTypeFactory(context));
             }
         }
         return mAccountTypeManager;
@@ -110,6 +118,37 @@
         }
     }
 
+    private static final AccountTypeManager EMPTY = new AccountTypeManager() {
+        @Override
+        public List<AccountWithDataSet> getAccounts(boolean contactWritableOnly) {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public void sortAccounts(AccountWithDataSet defaultAccount) {
+        }
+
+        @Override
+        public List<AccountWithDataSet> getGroupWritableAccounts() {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public AccountType getAccountType(AccountTypeWithDataSet accountTypeWithDataSet) {
+            return null;
+        }
+
+        @Override
+        public Map<AccountTypeWithDataSet, AccountType> getUsableInvitableAccountTypes() {
+            return null;
+        }
+
+        @Override
+        public List<AccountType> getAccountTypes(boolean contactWritableOnly) {
+            return Collections.emptyList();
+        }
+    };
+
     /**
      * Returns the list of all accounts (if contactWritableOnly is false) or just the list of
      * contact writable accounts (if contactWritableOnly is true).
@@ -183,6 +222,15 @@
         }
         return false;
     }
+
+    private static boolean hasRequiredPermissions(Context context) {
+        final boolean canGetAccounts = ContextCompat.checkSelfPermission(context,
+                android.Manifest.permission.GET_ACCOUNTS) == PackageManager.PERMISSION_GRANTED;
+        final boolean canReadContacts = ContextCompat.checkSelfPermission(context,
+                android.Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED;
+        return canGetAccounts && canReadContacts;
+    }
+
 }
 
 class AccountComparator implements Comparator<AccountWithDataSet> {
@@ -251,7 +299,7 @@
 
     private Context mContext;
     private AccountManager mAccountManager;
-    private DeviceAccountFilter mDeviceAccountFilter;
+    private DeviceLocalAccountTypeFactory mDeviceLocalAccountTypeFactory;
 
     private AccountType mFallbackAccountType;
 
@@ -306,10 +354,11 @@
     /**
      * Internal constructor that only performs initial parsing.
      */
-    public AccountTypeManagerImpl(Context context, DeviceAccountFilter deviceAccountFilter) {
+    public AccountTypeManagerImpl(Context context,
+            DeviceLocalAccountTypeFactory deviceLocalAccountTypeFactory) {
         mContext = context;
         mFallbackAccountType = new FallbackAccountType(context);
-        mDeviceAccountFilter = deviceAccountFilter;
+        mDeviceLocalAccountTypeFactory = deviceLocalAccountTypeFactory;
 
         mAccountManager = AccountManager.get(mContext);
 
@@ -351,6 +400,27 @@
 
         ContentResolver.addStatusChangeListener(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, this);
 
+        // Observe changes to RAW_CONTACTS so that we will update the list of "Device" accounts
+        // if a new device contact is added.
+        mContext.getContentResolver().registerContentObserver(
+                ContactsContract.RawContacts.CONTENT_URI, /* notifyDescendents */ true,
+                new ContentObserver(mListenerHandler) {
+            @Override
+            public boolean deliverSelfNotifications() {
+                return true;
+            }
+
+            @Override
+            public void onChange(boolean selfChange) {
+                mListenerHandler.sendEmptyMessage(MESSAGE_LOAD_DATA);
+            }
+
+            @Override
+            public void onChange(boolean selfChange, Uri uri) {
+                mListenerHandler.sendEmptyMessage(MESSAGE_LOAD_DATA);
+            }
+        });
+
         mListenerHandler.sendEmptyMessage(MESSAGE_LOAD_DATA);
     }
 
@@ -378,6 +448,7 @@
         if (latch == null) {
             return;
         }
+
         while (true) {
             try {
                 latch.await();
@@ -443,8 +514,11 @@
             } else if (SamsungAccountType.isSamsungAccountType(mContext, type,
                     auth.packageName)) {
                 accountType = new SamsungAccountType(mContext, auth.packageName, type);
-            } else if (mDeviceAccountFilter.isDeviceAccountType(type)) {
-                accountType = new FallbackAccountType(mContext);
+            } else if (!ExternalAccountType.hasContactsXml(mContext, auth.packageName)
+                    && isLocalAccountType(mDeviceLocalAccountTypeFactory, type)) {
+                // This will be loaded by the DeviceLocalAccountLocator so don't try to create an
+                // ExternalAccountType for it.
+                continue;
             } else {
                 Log.d(TAG, "Registering external account type=" + type
                         + ", packageName=" + auth.packageName);
@@ -460,13 +534,7 @@
                 }
             }
 
-            // TODO: this is a hack. For FallbackAccountType we want to use a default icon and
-            // label instead of what is pulled out of the authenticator
-            if (!(accountType instanceof FallbackAccountType)) {
-                accountType.accountType = auth.type;
-                accountType.titleRes = auth.labelId;
-                accountType.iconRes = auth.iconId;
-            }
+            accountType.initializeFieldsFromAuthenticator(auth);
 
             addAccountType(accountType, accountTypesByTypeAndDataSet, accountTypesByType);
 
@@ -505,6 +573,7 @@
         }
         timings.addSplit("Loaded account types");
 
+        boolean foundWritableGoogleAccount = false;
         // Map in accounts to associate the account names with each account type entry.
         Account[] accounts = mAccountManager.getAccounts();
         for (Account account : accounts) {
@@ -522,6 +591,10 @@
                         allAccounts.add(accountWithDataSet);
                         if (accountType.areContactsWritable()) {
                             contactWritableAccounts.add(accountWithDataSet);
+                            if (GoogleAccountType.ACCOUNT_TYPE.equals(account.type)
+                                    && accountWithDataSet.dataSet == null) {
+                                foundWritableGoogleAccount = true;
+                            }
                         }
                         if (accountType.isGroupMembershipEditable()) {
                             groupWritableAccounts.add(accountWithDataSet);
@@ -531,6 +604,40 @@
             }
         }
 
+        final DeviceLocalAccountLocator deviceAccounts =
+                new DeviceLocalAccountLocator(mContext.getContentResolver(),
+                        mDeviceLocalAccountTypeFactory,
+                        allAccounts);
+        final List<AccountWithDataSet> localAccounts = deviceAccounts.getDeviceLocalAccounts();
+        allAccounts.addAll(localAccounts);
+
+        for (AccountWithDataSet localAccount : localAccounts) {
+            // Prefer a known type if it exists. This covers the case that a local account has an
+            // authenticator with a valid contacts.xml
+            AccountType localAccountType = accountTypesByTypeAndDataSet.get(
+                    localAccount.getAccountTypeWithDataSet());
+            if (localAccountType == null) {
+                localAccountType = mDeviceLocalAccountTypeFactory.getAccountType(localAccount.type);
+            }
+            accountTypesByTypeAndDataSet.put(localAccount.getAccountTypeWithDataSet(),
+                    localAccountType);
+
+            // Skip the null account if there is a Google account available. This is done because
+            // the Google account's sync adapter will automatically move accounts in the "null"
+            // account.  Hence, it would be confusing to still show it as an available writable
+            // account since contacts that were saved to it would magically change accounts when the
+            // sync adapter runs.
+            if (foundWritableGoogleAccount && localAccount.type == null) {
+                continue;
+            }
+            if (localAccountType.areContactsWritable()) {
+                contactWritableAccounts.add(localAccount);
+            }
+            if (localAccountType.isGroupMembershipEditable()) {
+                groupWritableAccounts.add(localAccount);
+            }
+        }
+
         final AccountComparator accountComparator = new AccountComparator(null);
         Collections.sort(allAccounts, accountComparator);
         Collections.sort(contactWritableAccounts, accountComparator);
diff --git a/src/com/android/contacts/common/model/DeviceLocalAccountLocator.java b/src/com/android/contacts/common/model/DeviceLocalAccountLocator.java
new file mode 100644
index 0000000..8997ed4
--- /dev/null
+++ b/src/com/android/contacts/common/model/DeviceLocalAccountLocator.java
@@ -0,0 +1,115 @@
+/*
+ * 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.provider.ContactsContract;
+import android.support.annotation.VisibleForTesting;
+
+import com.android.contacts.common.model.account.AccountType;
+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;
+
+/**
+ * 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
+ */
+public class DeviceLocalAccountLocator {
+
+    @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 Set<String> mKnownAccountTypes;
+
+
+    public DeviceLocalAccountLocator(ContentResolver contentResolver,
+            DeviceLocalAccountTypeFactory factory,
+            List<AccountWithDataSet> knownAccounts) {
+        mResolver = contentResolver;
+        mAccountTypeFactory = factory;
+        mKnownAccountTypes = new HashSet<>();
+        for (AccountWithDataSet account : knownAccounts) {
+            mKnownAccountTypes.add(account.type);
+        }
+    }
+
+    public List<AccountWithDataSet> getDeviceLocalAccounts() {
+        final String[] selectionArgs = getSelectionArgs();
+        final Cursor cursor = mResolver.query(ContactsContract.RawContacts.CONTENT_URI, PROJECTION,
+                getSelection(), selectionArgs, null);
+
+        final Set<AccountWithDataSet> localAccounts = new HashSet<>();
+        try {
+            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)) {
+                    localAccounts.add(new AccountWithDataSet(name, type, dataSet));
+                }
+            }
+        } finally {
+            cursor.close();
+        }
+
+        return new ArrayList<>(localAccounts);
+    }
+
+    @VisibleForTesting
+    public String getSelection() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append(ContactsContract.RawContacts.DELETED).append(" =0 AND (")
+                .append(ContactsContract.RawContacts.ACCOUNT_TYPE).append(" IS NULL");
+        if (mKnownAccountTypes.isEmpty()) {
+            return sb.append(')').toString();
+        }
+        sb.append(" OR ").append(ContactsContract.RawContacts.ACCOUNT_TYPE).append(" NOT IN (");
+        for (String ignored : mKnownAccountTypes) {
+            sb.append("?,");
+        }
+        // Remove trailing ','
+        sb.deleteCharAt(sb.length() - 1).append(')').append(')');
+
+        return sb.toString();
+    }
+
+    @VisibleForTesting
+    public String[] getSelectionArgs() {
+        return mKnownAccountTypes.toArray(new String[mKnownAccountTypes.size()]);
+    }
+}
diff --git a/src/com/android/contacts/common/model/account/AccountDisplayInfo.java b/src/com/android/contacts/common/model/account/AccountDisplayInfo.java
new file mode 100644
index 0000000..f68fdd5
--- /dev/null
+++ b/src/com/android/contacts/common/model/account/AccountDisplayInfo.java
@@ -0,0 +1,100 @@
+/*
+ * 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.account;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.StringRes;
+import android.text.TextUtils;
+
+/**
+ * Wrapper around AccountWithDataSet that contains user-friendly labels and an icon.
+ *
+ * The raw values for name and type in AccountWithDataSet are not always (or even usually)
+ * appropriate for direct display to the user.
+ */
+public class AccountDisplayInfo {
+    private final AccountWithDataSet mSource;
+
+    private final CharSequence mName;
+    private final CharSequence mType;
+    private final Drawable mIcon;
+
+    private final boolean mIsDeviceAccount;
+
+    public AccountDisplayInfo(AccountWithDataSet account, CharSequence name, CharSequence type,
+            Drawable icon, boolean isDeviceAccount) {
+        mSource = account;
+        mName = name;
+        mType = type;
+        mIcon = icon;
+        mIsDeviceAccount = isDeviceAccount;
+    }
+
+    public AccountWithDataSet getSource() {
+        return mSource;
+    }
+
+    public CharSequence getNameLabel() {
+        return mName;
+    }
+
+    public CharSequence getTypeLabel() {
+        return mType;
+    }
+
+    public Drawable getIcon() {
+        return mIcon;
+    }
+
+    public boolean hasGoogleAccountType() {
+        return GoogleAccountType.ACCOUNT_TYPE.equals(mSource.type);
+    }
+
+    public boolean isGoogleAccount() {
+        return GoogleAccountType.ACCOUNT_TYPE.equals(mSource.type) && mSource.dataSet == null;
+    }
+
+    public boolean isDeviceAccount() {
+        return mIsDeviceAccount;
+    }
+
+    public boolean hasDistinctName() {
+        return !TextUtils.equals(mName, mType);
+    }
+
+    public AccountDisplayInfo withName(CharSequence name) {
+        return withNameAndType(name, mType);
+    }
+
+    public AccountDisplayInfo withType(CharSequence type) {
+        return withNameAndType(mName, type);
+    }
+
+    public AccountDisplayInfo withNameAndType(CharSequence name, CharSequence type) {
+        return new AccountDisplayInfo(mSource, name, type, mIcon, mIsDeviceAccount);
+    }
+
+    public AccountDisplayInfo formatted(Context context, @StringRes int nameFormat,
+            @StringRes int typeFormat) {
+        return new AccountDisplayInfo(mSource, context.getString(nameFormat, mName),
+                context.getString(typeFormat, mType), mIcon, mIsDeviceAccount);
+    }
+
+    public AccountDisplayInfo withFormattedName(Context context, @StringRes int nameFormat) {
+        return withName(context.getString(nameFormat, mName));
+    }
+}
diff --git a/src/com/android/contacts/common/model/account/AccountDisplayInfoFactory.java b/src/com/android/contacts/common/model/account/AccountDisplayInfoFactory.java
new file mode 100644
index 0000000..aad1689
--- /dev/null
+++ b/src/com/android/contacts/common/model/account/AccountDisplayInfoFactory.java
@@ -0,0 +1,122 @@
+/*
+ * 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.account;
+
+import android.content.Context;
+
+import com.android.contacts.common.list.ContactListFilter;
+import com.android.contacts.common.model.AccountTypeManager;
+import com.android.contacts.common.model.RawContactDelta;
+import com.android.contacts.common.util.DeviceLocalAccountTypeFactory;
+import com.android.contactsbind.ObjectFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Provides methods to get AccountDisplayInfo instances for available accounts.
+ *
+ * For most accounts the account name will be used for the label but device accounts and
+ * SIM accounts have friendly names associated with them unless there is more than one of these
+ * types of accounts present in the list.
+ */
+public class AccountDisplayInfoFactory {
+
+    private final Context mContext;
+    private final AccountTypeManager mAccountTypeManager;
+
+    private final DeviceLocalAccountTypeFactory mDeviceAccountTypeFactory;
+
+    private final int mDeviceAccountCount;
+    private final int mSimAccountCount;
+
+    public AccountDisplayInfoFactory(Context context, List<AccountWithDataSet> accounts) {
+        this(context, AccountTypeManager.getInstance(context),
+                ObjectFactory.getDeviceLocalAccountTypeFactory(context), accounts);
+    }
+
+    public AccountDisplayInfoFactory(Context context, AccountTypeManager accountTypeManager,
+            DeviceLocalAccountTypeFactory deviceAccountTypeFactory,
+            List<AccountWithDataSet> accounts) {
+        mContext = context;
+        mAccountTypeManager = accountTypeManager;
+        mDeviceAccountTypeFactory = deviceAccountTypeFactory;
+
+        mSimAccountCount = countOfType(DeviceLocalAccountTypeFactory.TYPE_SIM, accounts);
+        mDeviceAccountCount = countOfType(DeviceLocalAccountTypeFactory.TYPE_DEVICE, accounts);
+    }
+
+    public AccountDisplayInfo getAccountDisplayInfo(AccountWithDataSet account) {
+        final AccountType type = mAccountTypeManager.getAccountTypeForAccount(account);
+        final CharSequence name = shouldUseTypeLabelForName(account)
+                ? type.getDisplayLabel(mContext)
+                : account.name;
+        return new AccountDisplayInfo(account, name, type.getDisplayLabel(mContext),
+                type.getDisplayIcon(mContext),
+                DeviceLocalAccountTypeFactory.Util.isLocalAccountType(mDeviceAccountTypeFactory,
+                        type.accountType));
+    }
+
+    public AccountDisplayInfo getAccountDisplayInfoFor(ContactListFilter filter) {
+        return getAccountDisplayInfo(filter.toAccountWithDataSet());
+    }
+
+    public AccountDisplayInfo getAccountDisplayInfoFor(RawContactDelta delta) {
+        final AccountWithDataSet account = new AccountWithDataSet(delta.getAccountName(),
+                delta.getAccountType(), delta.getDataSet());
+        return getAccountDisplayInfo(account);
+    }
+
+    public static AccountDisplayInfoFactory fromListFilters(Context context,
+            List<ContactListFilter> filters) {
+        final List<AccountWithDataSet> accounts = new ArrayList<>(filters.size());
+        for (ContactListFilter filter : filters) {
+            accounts.add(filter.toAccountWithDataSet());
+        }
+        return new AccountDisplayInfoFactory(context, accounts);
+    }
+
+    public static AccountDisplayInfoFactory forAllAccounts(Context context) {
+        final AccountTypeManager accountTypeManager = AccountTypeManager.getInstance(context);
+        final List<AccountWithDataSet> accounts = accountTypeManager.getAccounts(false);
+        return new AccountDisplayInfoFactory(context, accounts);
+    }
+
+    public static AccountDisplayInfoFactory forWritableAccounts(Context context) {
+        final AccountTypeManager accountTypeManager = AccountTypeManager.getInstance(context);
+        final List<AccountWithDataSet> accounts = accountTypeManager.getAccounts(true);
+        return new AccountDisplayInfoFactory(context, accounts);
+    }
+
+    private boolean shouldUseTypeLabelForName(AccountWithDataSet account) {
+        final int type = mDeviceAccountTypeFactory.classifyAccount(account.type);
+        return (type == DeviceLocalAccountTypeFactory.TYPE_SIM && mSimAccountCount == 1)
+                || (type == DeviceLocalAccountTypeFactory.TYPE_DEVICE && mDeviceAccountCount == 1)
+                || account.name == null;
+
+    }
+
+    private int countOfType(@DeviceLocalAccountTypeFactory.LocalAccountType int type,
+            List<AccountWithDataSet> accounts) {
+        int count = 0;
+        for (AccountWithDataSet account : accounts) {
+            if (mDeviceAccountTypeFactory.classifyAccount(account.type) == type) {
+                count++;
+            }
+        }
+        return count;
+    }
+}
diff --git a/src/com/android/contacts/common/model/account/AccountType.java b/src/com/android/contacts/common/model/account/AccountType.java
index 8b50d79..0943953 100644
--- a/src/com/android/contacts/common/model/account/AccountType.java
+++ b/src/com/android/contacts/common/model/account/AccountType.java
@@ -16,6 +16,7 @@
 
 package com.android.contacts.common.model.account;
 
+import android.accounts.AuthenticatorDescription;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -325,6 +326,12 @@
         return this.mMimeKinds.get(mimeType);
     }
 
+    public void initializeFieldsFromAuthenticator(AuthenticatorDescription authenticator) {
+        accountType = authenticator.type;
+        titleRes = authenticator.labelId;
+        iconRes = authenticator.iconId;
+    }
+
     /**
      * Add given {@link DataKind} to list of those provided by this source.
      */
diff --git a/src/com/android/contacts/common/model/account/AccountWithDataSet.java b/src/com/android/contacts/common/model/account/AccountWithDataSet.java
index 5947647..887cdd4 100644
--- a/src/com/android/contacts/common/model/account/AccountWithDataSet.java
+++ b/src/com/android/contacts/common/model/account/AccountWithDataSet.java
@@ -77,6 +77,8 @@
         mAccountTypeWithDataSet = AccountTypeWithDataSet.get(type, dataSet);
     }
 
+    // TODO: consider modifying or deleting this method. "local" accounts on some non-nexus devices
+    // have non-null values for name, type, and dataset
     public boolean isLocalAccount() {
         return name == null && type == null && dataSet == null;
     }
diff --git a/src/com/android/contacts/common/model/account/DeviceLocalAccountType.java b/src/com/android/contacts/common/model/account/DeviceLocalAccountType.java
new file mode 100644
index 0000000..31452e6
--- /dev/null
+++ b/src/com/android/contacts/common/model/account/DeviceLocalAccountType.java
@@ -0,0 +1,37 @@
+/*
+ * 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.account;
+
+import android.content.Context;
+
+public class DeviceLocalAccountType extends FallbackAccountType {
+
+    private final boolean mGroupsEditable;
+
+    public DeviceLocalAccountType(Context context, boolean groupsEditable) {
+        super(context);
+        mGroupsEditable = groupsEditable;
+    }
+
+    public DeviceLocalAccountType(Context context) {
+        this(context, false);
+    }
+
+    @Override
+    public boolean isGroupMembershipEditable() {
+        return mGroupsEditable;
+    }
+}
diff --git a/src/com/android/contacts/common/model/account/FallbackAccountType.java b/src/com/android/contacts/common/model/account/FallbackAccountType.java
index 8a7b964..7c6d17c 100644
--- a/src/com/android/contacts/common/model/account/FallbackAccountType.java
+++ b/src/com/android/contacts/common/model/account/FallbackAccountType.java
@@ -16,6 +16,7 @@
 
 package com.android.contacts.common.model.account;
 
+import android.accounts.AuthenticatorDescription;
 import android.content.Context;
 import android.util.Log;
 
@@ -71,6 +72,12 @@
     }
 
     @Override
+    public void initializeFieldsFromAuthenticator(AuthenticatorDescription authenticator) {
+        // Do nothing. For "Device" accounts we want to just display them using our own strings
+        // and icons.
+    }
+
+    @Override
     public boolean areContactsWritable() {
         return true;
     }
diff --git a/src/com/android/contacts/common/model/account/SimAccountType.java b/src/com/android/contacts/common/model/account/SimAccountType.java
new file mode 100644
index 0000000..a2219cc
--- /dev/null
+++ b/src/com/android/contacts/common/model/account/SimAccountType.java
@@ -0,0 +1,42 @@
+/*
+ * 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.account;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+
+import com.android.contacts.R;
+
+/**
+ * Account type for SIM card contacts
+ *
+ * TODO: Right now this is the same as FallbackAccountType with a different icon and label.
+ * Instead it should setup it's own DataKinds that are known to work on SIM card.
+ */
+public class SimAccountType extends FallbackAccountType {
+
+    public SimAccountType(Context context) {
+        super(context);
+        this.titleRes = R.string.account_sim;
+        this.iconRes = R.drawable.ic_sim_card_tinted_24dp;
+
+    }
+
+    @Override
+    public boolean isGroupMembershipEditable() {
+        return false;
+    }
+}
diff --git a/src/com/android/contacts/common/preference/ContactsPreferences.java b/src/com/android/contacts/common/preference/ContactsPreferences.java
index a8a9089..f994d28 100644
--- a/src/com/android/contacts/common/preference/ContactsPreferences.java
+++ b/src/com/android/contacts/common/preference/ContactsPreferences.java
@@ -88,7 +88,7 @@
     private final Context mContext;
     private int mSortOrder = PREFERENCE_UNASSIGNED;
     private int mDisplayOrder = PREFERENCE_UNASSIGNED;
-    private String mDefaultAccount = null;
+    private AccountWithDataSet mDefaultAccount = null;
     private ChangeListener mListener = null;
     private Handler mHandler;
     private final SharedPreferences mPreferences;
@@ -169,26 +169,24 @@
         return mContext.getResources().getBoolean(R.bool.config_default_account_user_changeable);
     }
 
-    public String getDefaultAccount() {
+    public AccountWithDataSet getDefaultAccount() {
         if (!isDefaultAccountUserChangeable()) {
             return mDefaultAccount;
         }
-        if (TextUtils.isEmpty(mDefaultAccount)) {
+        if (mDefaultAccount == null) {
             final String accountString = mPreferences
-                    .getString(mDefaultAccountKey, mDefaultAccount);
+                    .getString(mDefaultAccountKey, null);
             if (!TextUtils.isEmpty(accountString)) {
-                final AccountWithDataSet accountWithDataSet = AccountWithDataSet.unstringify(
-                        accountString);
-                mDefaultAccount = accountWithDataSet.name;
+                mDefaultAccount = AccountWithDataSet.unstringify(accountString);
             }
         }
         return mDefaultAccount;
     }
 
     public void setDefaultAccount(AccountWithDataSet accountWithDataSet) {
-        mDefaultAccount = accountWithDataSet == null ? null : accountWithDataSet.name;
+        mDefaultAccount = accountWithDataSet;
         final Editor editor = mPreferences.edit();
-        if (TextUtils.isEmpty(mDefaultAccount)) {
+        if (mDefaultAccount == null) {
             editor.remove(mDefaultAccountKey);
         } else {
             editor.putString(mDefaultAccountKey, accountWithDataSet.stringify());
diff --git a/src/com/android/contacts/common/preference/DefaultAccountPreference.java b/src/com/android/contacts/common/preference/DefaultAccountPreference.java
index bcde371..3960e62 100644
--- a/src/com/android/contacts/common/preference/DefaultAccountPreference.java
+++ b/src/com/android/contacts/common/preference/DefaultAccountPreference.java
@@ -19,21 +19,19 @@
 import android.app.AlertDialog;
 import android.content.Context;
 import android.content.DialogInterface;
-import android.preference.ListPreference;
+import android.preference.DialogPreference;
 import android.util.AttributeSet;
 import android.view.View;
 
+import com.android.contacts.common.model.account.AccountDisplayInfoFactory;
 import com.android.contacts.common.model.account.AccountWithDataSet;
 import com.android.contacts.common.util.AccountsListAdapter;
 
-import java.util.HashMap;
-import java.util.Map;
-
-public class DefaultAccountPreference extends ListPreference {
+public class DefaultAccountPreference extends DialogPreference {
     private ContactsPreferences mPreferences;
-    private Map<String, AccountWithDataSet> mAccountMap;
-    private int mClickedDialogEntryIndex;
     private AccountsListAdapter mListAdapter;
+    private AccountDisplayInfoFactory mAccountDisplayInfoFactory;
+    private int mChosenIndex = -1;
 
     public DefaultAccountPreference(Context context) {
         super(context);
@@ -53,25 +51,9 @@
 
     private void prepare() {
         mPreferences = new ContactsPreferences(getContext());
-        mAccountMap = new HashMap<>();
         mListAdapter = new AccountsListAdapter(getContext(),
                 AccountsListAdapter.AccountListFilter.ACCOUNTS_CONTACT_WRITABLE);
-        final String[] accountNamesArray = new String[mListAdapter.getCount()];
-        for (int i = 0; i < mListAdapter.getCount(); i++) {
-            final AccountWithDataSet account = mListAdapter.getItem(i);
-            mAccountMap.put(account.name, account);
-            accountNamesArray[i] = account.name;
-        }
-        setEntries(accountNamesArray);
-        setEntryValues(accountNamesArray);
-        final String defaultAccount = String.valueOf(mPreferences.getDefaultAccount());
-        if (mListAdapter.getCount() == 1) {
-            setValue(mListAdapter.getItem(0).name);
-        } else if (mAccountMap.keySet().contains(defaultAccount)) {
-            setValue(defaultAccount);
-        } else {
-            setValue(null);
-        }
+        mAccountDisplayInfoFactory = AccountDisplayInfoFactory.forWritableAccounts(getContext());
     }
 
     @Override
@@ -81,48 +63,39 @@
 
     @Override
     public CharSequence getSummary() {
-        return mPreferences.getDefaultAccount();
-    }
-
-    @Override
-    protected boolean persistString(String value) {
-        if (value == null && mPreferences.getDefaultAccount() == null) {
-            return true;
-        }
-        if (value == null || mPreferences.getDefaultAccount() == null
-                || !value.equals(mPreferences.getDefaultAccount())) {
-            mPreferences.setDefaultAccount(mAccountMap.get(value));
-            notifyChanged();
-        }
-        return true;
+        final AccountWithDataSet defaultAccount = mPreferences.getDefaultAccount();
+        return defaultAccount == null ? null : mAccountDisplayInfoFactory
+                .getAccountDisplayInfo(defaultAccount).getNameLabel();
     }
 
     @Override
     protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
         super.onPrepareDialogBuilder(builder);
-        // UX recommendation is not to show cancel button on such lists.
+        // UX recommendation is not to show buttons on such lists.
         builder.setNegativeButton(null, null);
-        // Override and do everything ListPreference does except relative to our custom adapter.
-        // onDialogClosed needs to be overridden as well since mClickedDialogEntryIndex is private
-        // in ListPreference.
+        builder.setPositiveButton(null, null);
         builder.setAdapter(mListAdapter, new DialogInterface.OnClickListener() {
             @Override
             public void onClick(DialogInterface dialog, int which) {
-                mClickedDialogEntryIndex = which;
-                // Clicking on an item simulates the positive button click,
-                // and dismisses the dialog.
-                DefaultAccountPreference.this.onClick(dialog, DialogInterface.BUTTON_POSITIVE);
-                dialog.dismiss();
+                mChosenIndex = which;
             }
         });
     }
 
     @Override
     protected void onDialogClosed(boolean positiveResult) {
-        if (positiveResult && mClickedDialogEntryIndex >= 0 && getEntryValues() != null) {
-            final String value = getEntryValues()[mClickedDialogEntryIndex].toString();
-            if (callChangeListener(value)) {
-                setValue(value);
+        final AccountWithDataSet currentDefault = mPreferences.getDefaultAccount();
+
+        if (mChosenIndex == -1) {
+            if (currentDefault != null) {
+                mPreferences.setDefaultAccount(null);
+                notifyChanged();
+            }
+        } else {
+            final AccountWithDataSet chosenAccount = mListAdapter.getItem(mChosenIndex);
+            if (!chosenAccount.equals(currentDefault)) {
+                mPreferences.setDefaultAccount(chosenAccount);
+                notifyChanged();
             }
         }
     }
diff --git a/src/com/android/contacts/common/util/AccountsListAdapter.java b/src/com/android/contacts/common/util/AccountsListAdapter.java
index ef43a30..a437929 100644
--- a/src/com/android/contacts/common/util/AccountsListAdapter.java
+++ b/src/com/android/contacts/common/util/AccountsListAdapter.java
@@ -17,7 +17,6 @@
 package com.android.contacts.common.util;
 
 import android.content.Context;
-import android.text.TextUtils.TruncateAt;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -25,9 +24,10 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import com.android.contacts.common.model.account.AccountDisplayInfo;
+import com.android.contacts.common.model.account.AccountDisplayInfoFactory;
 import com.android.contacts.common.R;
 import com.android.contacts.common.model.AccountTypeManager;
-import com.android.contacts.common.model.account.AccountType;
 import com.android.contacts.common.model.account.AccountWithDataSet;
 
 import java.util.ArrayList;
@@ -38,8 +38,7 @@
  */
 public final class AccountsListAdapter extends BaseAdapter {
     private final LayoutInflater mInflater;
-    private final List<AccountWithDataSet> mAccounts;
-    private final AccountTypeManager mAccountTypes;
+    private final List<AccountDisplayInfo> mAccountDisplayInfoList;
     private final Context mContext;
     private int mCustomLayout = -1;
 
@@ -63,22 +62,29 @@
     public AccountsListAdapter(Context context, AccountListFilter accountListFilter,
             AccountWithDataSet currentAccount) {
         mContext = context;
-        mAccountTypes = AccountTypeManager.getInstance(context);
-        mAccounts = getAccounts(accountListFilter);
+        final List<AccountWithDataSet> accounts = getAccounts(accountListFilter);
         if (currentAccount != null
-                && !mAccounts.isEmpty()
-                && !mAccounts.get(0).equals(currentAccount)
-                && mAccounts.remove(currentAccount)) {
-            mAccounts.add(0, currentAccount);
+                && !accounts.isEmpty()
+                && !accounts.get(0).equals(currentAccount)
+                && accounts.remove(currentAccount)) {
+            accounts.add(0, currentAccount);
+        }
+
+        final AccountDisplayInfoFactory factory = new AccountDisplayInfoFactory(context,
+                accounts);
+        mAccountDisplayInfoList = new ArrayList<>(accounts.size());
+        for (AccountWithDataSet account : accounts) {
+            mAccountDisplayInfoList.add(factory.getAccountDisplayInfo(account));
         }
         mInflater = LayoutInflater.from(context);
     }
 
     private List<AccountWithDataSet> getAccounts(AccountListFilter accountListFilter) {
+        final AccountTypeManager typeManager = AccountTypeManager.getInstance(mContext);
         if (accountListFilter == AccountListFilter.ACCOUNTS_GROUP_WRITABLE) {
-            return new ArrayList<AccountWithDataSet>(mAccountTypes.getGroupWritableAccounts());
+            return new ArrayList<AccountWithDataSet>(typeManager.getGroupWritableAccounts());
         }
-        return new ArrayList<AccountWithDataSet>(mAccountTypes.getAccounts(
+        return new ArrayList<AccountWithDataSet>(typeManager.getAccounts(
                 accountListFilter == AccountListFilter.ACCOUNTS_CONTACT_WRITABLE));
     }
 
@@ -96,25 +102,22 @@
         final TextView text2 = (TextView) resultView.findViewById(android.R.id.text2);
         final ImageView icon = (ImageView) resultView.findViewById(android.R.id.icon);
 
-        final AccountWithDataSet account = mAccounts.get(position);
-        final AccountType accountType = mAccountTypes.getAccountType(account.type, account.dataSet);
+        text1.setText(mAccountDisplayInfoList.get(position).getTypeLabel());
+        text2.setText(mAccountDisplayInfoList.get(position).getNameLabel());
 
-        text1.setText(accountType.getDisplayLabel(mContext));
-        text2.setText(account.name);
-
-        icon.setImageDrawable(accountType.getDisplayIcon(mContext));
+        icon.setImageDrawable(mAccountDisplayInfoList.get(position).getIcon());
 
         return resultView;
     }
 
     @Override
     public int getCount() {
-        return mAccounts.size();
+        return mAccountDisplayInfoList.size();
     }
 
     @Override
     public AccountWithDataSet getItem(int position) {
-        return mAccounts.get(position);
+        return mAccountDisplayInfoList.get(position).getSource();
     }
 
     @Override
diff --git a/src/com/android/contacts/common/util/DeviceAccountFilter.java b/src/com/android/contacts/common/util/DeviceAccountFilter.java
deleted file mode 100644
index 9dc98a5..0000000
--- a/src/com/android/contacts/common/util/DeviceAccountFilter.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * 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.util;
-
-/**
- * Reports whether a value from RawContacts.ACCOUNT_TYPE should be considered a "Device"
- * account
- */
-public interface DeviceAccountFilter {
-    boolean isDeviceAccountType(String accountType);
-
-    public static DeviceAccountFilter ONLY_NULL = new DeviceAccountFilter() {
-        @Override
-        public boolean isDeviceAccountType(String accountType) {
-            return accountType == null;
-        }
-    };
-}
diff --git a/src/com/android/contacts/common/util/DeviceAccountPresentationValues.java b/src/com/android/contacts/common/util/DeviceAccountPresentationValues.java
deleted file mode 100644
index dab81ed..0000000
--- a/src/com/android/contacts/common/util/DeviceAccountPresentationValues.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * 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.util;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-
-import com.android.contacts.common.list.ContactListFilter;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Supplies the label and icon that should be used for device accounts in the Nav Drawer.
- *
- * This operates on the list of filters to allow the implementation to choose better resources
- * in the case that there are multiple device accounts in the filter list.
- */
-public interface DeviceAccountPresentationValues {
-    void setFilters(List<ContactListFilter> filters);
-
-    CharSequence getLabel(int index);
-
-    Drawable getIcon(int index);
-
-    /**
-     * The default implementation only returns a label and icon for a device filter that as null
-     * values for the accountType and accountName
-     */
-    class Default implements DeviceAccountPresentationValues {
-        private final Context mContext;
-
-        private List<ContactListFilter> mFilters = null;
-
-        public Default(Context context) {
-            mContext = context;
-        }
-
-        @Override
-        public CharSequence getLabel(int index) {
-            assertFiltersInitialized();
-
-            final ContactListFilter filter = mFilters.get(index);
-            if (filter.filterType != ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS) {
-                return filter.accountName;
-            }
-            return filter.accountName != null ? filter.accountName :
-                    mContext.getString(com.android.contacts.common.R.string.account_phone);
-        }
-
-        @Override
-        public Drawable getIcon(int index) {
-            assertFiltersInitialized();
-
-            final ContactListFilter filter = mFilters.get(index);
-            if (filter.filterType != ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS) {
-                return filter.icon;
-            }
-            return mContext.getDrawable(com.android.contacts.common.R.drawable.ic_device);
-        }
-
-        @Override
-        public void setFilters(List<ContactListFilter> filters) {
-            if (filters == null) {
-                mFilters = Collections.emptyList();
-            } else {
-                mFilters = filters;
-            }
-        }
-
-        private void assertFiltersInitialized() {
-            if (mFilters == null) {
-                throw new IllegalStateException("setFilters must be called first.");
-            }
-        }
-    }
-
-}
diff --git a/src/com/android/contacts/common/util/DeviceLocalAccountTypeFactory.java b/src/com/android/contacts/common/util/DeviceLocalAccountTypeFactory.java
new file mode 100644
index 0000000..040a6b4
--- /dev/null
+++ b/src/com/android/contacts/common/util/DeviceLocalAccountTypeFactory.java
@@ -0,0 +1,80 @@
+/*
+ * 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.util;
+
+import android.content.Context;
+import android.support.annotation.IntDef;
+
+import com.android.contacts.common.model.account.AccountType;
+import com.android.contacts.common.model.account.DeviceLocalAccountType;
+import com.android.contacts.common.model.account.FallbackAccountType;
+
+import java.lang.annotation.Retention;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Reports whether a value from RawContacts.ACCOUNT_TYPE should be considered a "Device"
+ * account
+ */
+public interface DeviceLocalAccountTypeFactory {
+
+    @Retention(SOURCE)
+    @IntDef({TYPE_OTHER, TYPE_DEVICE, TYPE_SIM})
+    @interface LocalAccountType {}
+    static final int TYPE_OTHER = 0;
+    static final int TYPE_DEVICE = 1;
+    static final int TYPE_SIM = 2;
+
+    @DeviceLocalAccountTypeFactory.LocalAccountType int classifyAccount(String accountType);
+
+    AccountType getAccountType(String accountType);
+
+    class Util {
+        private Util() { }
+
+        public static boolean isLocalAccountType(@LocalAccountType int type) {
+            return type == TYPE_SIM || type == TYPE_DEVICE;
+        }
+
+        public static boolean isLocalAccountType(DeviceLocalAccountTypeFactory factory,
+                String type) {
+
+            return isLocalAccountType(factory.classifyAccount(type));
+        }
+    }
+
+    class Default implements DeviceLocalAccountTypeFactory {
+        private Context mContext;
+
+        public Default(Context context) {
+            mContext = context;
+        }
+
+        @Override
+        public int classifyAccount(String accountType) {
+            return accountType == null ? TYPE_DEVICE : TYPE_OTHER;
+        }
+
+        @Override
+        public AccountType getAccountType(String accountType) {
+            if (accountType != null) {
+                throw new IllegalArgumentException(accountType + " is not a device account type.");
+            }
+            return new DeviceLocalAccountType(mContext);
+        }
+    }
+}
diff --git a/src/com/android/contacts/common/util/DeviceLocalContactsFilterProvider.java b/src/com/android/contacts/common/util/DeviceLocalContactsFilterProvider.java
deleted file mode 100644
index 1d06a43..0000000
--- a/src/com/android/contacts/common/util/DeviceLocalContactsFilterProvider.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * 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.util;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.app.LoaderManager;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.CursorLoader;
-import android.content.Loader;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.provider.ContactsContract;
-import android.support.annotation.Keep;
-import android.support.annotation.VisibleForTesting;
-
-import com.android.contacts.common.list.ContactListFilter;
-import com.android.contacts.test.NeededForReflection;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Get filters for device local accounts. These are "accounts" that have contacts associated
- * with them but are not returned by AccountManager. Any other account will be displayed
- * automatically so we don't worry about it.
- */
-public class DeviceLocalContactsFilterProvider
-        implements LoaderManager.LoaderCallbacks<Cursor> {
-
-    public static String[] PROJECTION = new String[] {
-            ContactsContract.RawContacts.ACCOUNT_NAME, ContactsContract.RawContacts.ACCOUNT_TYPE
-    };
-
-    private static final int COL_NAME = 0;
-    private static final int COL_TYPE = 1;
-
-    private final Context mContext;
-    private final DeviceAccountFilter mAccountTypeFilter;
-
-    private String[] mKnownAccountTypes;
-
-    private List<ContactListFilter> mDeviceFilters = Collections.emptyList();
-
-    public DeviceLocalContactsFilterProvider(Context context,
-            DeviceAccountFilter accountTypeFilter) {
-        mContext = context;
-        mAccountTypeFilter = accountTypeFilter;
-    }
-
-    private ContactListFilter createFilterForAccount(Account account) {
-        return new ContactListFilter(ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS,
-                account.type, account.name, null, null);
-    }
-
-    public List<ContactListFilter> getListFilters() {
-        return mDeviceFilters;
-    }
-
-    @Override
-    public CursorLoader onCreateLoader(int i, Bundle bundle) {
-        if (mKnownAccountTypes == null) {
-            initKnownAccountTypes();
-        }
-        return new CursorLoader(mContext, getUri(), PROJECTION, getSelection(),
-                getSelectionArgs(), null);
-    }
-
-
-    private List<ContactListFilter> createFiltersFromResults(Cursor cursor) {
-        final Set<Account> accounts = new HashSet<>();
-        boolean hasNullType = false;
-
-        while (cursor.moveToNext()) {
-            final String name = cursor.getString(COL_NAME);
-            final String type = cursor.getString(COL_TYPE);
-            // The case where where only one of the columns is null isn't handled specifically.
-            if (mAccountTypeFilter.isDeviceAccountType(type)) {
-                if (name != null && type != null) {
-                    accounts.add(new Account(name, type));
-                } else {
-                    hasNullType = true;
-                }
-            }
-        }
-
-        final List<ContactListFilter> result = new ArrayList<>(accounts.size());
-        if (hasNullType) {
-            result.add(new ContactListFilter(ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS,
-                    null, null, null, null));
-        }
-        for (Account account : accounts) {
-            result.add(createFilterForAccount(account));
-        }
-        return result;
-    }
-
-    @Override
-    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
-        if (cursor == null) return;
-        mDeviceFilters = createFiltersFromResults(cursor);
-    }
-
-    @Override
-    public void onLoaderReset(Loader<Cursor> loader) {
-    }
-
-    @Keep
-    @VisibleForTesting
-    public void setKnownAccountTypes(String... accountTypes) {
-        mKnownAccountTypes = accountTypes;
-    }
-
-    private void initKnownAccountTypes() {
-        final AccountManager accountManager = (AccountManager) mContext
-                .getSystemService(Context.ACCOUNT_SERVICE);
-        final Set<String> knownTypes = new HashSet<>();
-        final Account[] accounts = accountManager.getAccounts();
-        for (Account account : accounts) {
-            if (ContentResolver.getIsSyncable(account, ContactsContract.AUTHORITY) > 0) {
-                knownTypes.add(account.type);
-            }
-        }
-        mKnownAccountTypes = knownTypes.toArray(new String[knownTypes.size()]);
-    }
-
-    private Uri getUri() {
-        final Uri.Builder builder = ContactsContract.RawContacts.CONTENT_URI.buildUpon();
-        if (mKnownAccountTypes == null || mKnownAccountTypes.length == 0) {
-            builder.appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY, "1");
-        }
-        return builder.build();
-    }
-
-    private String getSelection() {
-        final StringBuilder sb = new StringBuilder();
-        sb.append(ContactsContract.RawContacts.DELETED).append(" =0 AND (")
-                .append(ContactsContract.RawContacts.ACCOUNT_TYPE).append(" IS NULL");
-        if (mKnownAccountTypes == null || mKnownAccountTypes.length == 0) {
-            return sb.append(')').toString();
-        }
-        sb.append(" OR ").append(ContactsContract.RawContacts.ACCOUNT_TYPE).append(" NOT IN (");
-        for (String ignored : mKnownAccountTypes) {
-            sb.append("?,");
-        }
-        // Remove trailing ','
-        sb.deleteCharAt(sb.length() - 1).append(')').append(')');
-
-        return sb.toString();
-    }
-
-    private String[] getSelectionArgs() {
-        return mKnownAccountTypes;
-    }
-}
diff --git a/src/com/android/contacts/editor/CompactRawContactsEditorView.java b/src/com/android/contacts/editor/CompactRawContactsEditorView.java
index d9cc58d..884cc51 100644
--- a/src/com/android/contacts/editor/CompactRawContactsEditorView.java
+++ b/src/com/android/contacts/editor/CompactRawContactsEditorView.java
@@ -16,6 +16,8 @@
 
 package com.android.contacts.editor;
 
+import com.android.contacts.common.model.account.AccountDisplayInfo;
+import com.android.contacts.common.model.account.AccountDisplayInfoFactory;
 import com.android.contacts.R;
 import com.android.contacts.common.model.AccountTypeManager;
 import com.android.contacts.common.model.RawContactDelta;
@@ -319,6 +321,7 @@
     private CompactRawContactsEditorView.Listener mListener;
 
     private AccountTypeManager mAccountTypeManager;
+    private AccountDisplayInfoFactory mAccountDisplayInfoFactory;
     private LayoutInflater mLayoutInflater;
 
     private ViewIdGenerator mViewIdGenerator;
@@ -372,6 +375,7 @@
         super.onFinishInflate();
 
         mAccountTypeManager = AccountTypeManager.getInstance(getContext());
+        mAccountDisplayInfoFactory = AccountDisplayInfoFactory.forWritableAccounts(getContext());
         mLayoutInflater = (LayoutInflater)
                 getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
 
@@ -727,26 +731,25 @@
         final RawContactDelta rawContactDelta =
                 mPrimaryNameKindSectionData.first.getRawContactDelta();
 
+        final AccountDisplayInfo account =
+                mAccountDisplayInfoFactory.getAccountDisplayInfoFor(rawContactDelta);
+
         // Get the account information for the primary raw contact delta
-        final Pair<String,String> accountInfo = mIsUserProfile
-                ? EditorUiUtils.getLocalAccountInfo(getContext(),
-                        rawContactDelta.getAccountName(),
-                        rawContactDelta.getAccountType(mAccountTypeManager))
-                : EditorUiUtils.getAccountInfo(getContext(),
-                        rawContactDelta.getAccountName(),
-                        rawContactDelta.getAccountType(mAccountTypeManager));
+        final String accountLabel = mIsUserProfile
+                ? EditorUiUtils.getAccountHeaderLabelForMyProfile(getContext(), account)
+                : account.getNameLabel().toString();
 
         // Either the account header or selector should be shown, not both.
         final List<AccountWithDataSet> accounts =
                 AccountTypeManager.getInstance(getContext()).getAccounts(true);
         if (mHasNewContact && !mIsUserProfile) {
             if (accounts.size() > 1) {
-                addAccountSelector(accountInfo, rawContactDelta);
+                addAccountSelector(rawContactDelta, accountLabel);
             } else {
-                addAccountHeader(accountInfo);
+                addAccountHeader(accountLabel);
             }
         } else if (mIsUserProfile || !shouldHideAccountContainer(rawContactDeltas)) {
-            addAccountHeader(accountInfo);
+            addAccountHeader(accountLabel);
         }
 
         // The raw contact selector should only display linked raw contacts that can be edited in
@@ -800,14 +803,12 @@
         return (writable > 1 || (writable > 0 && readonly > 0));
     }
 
-    private void addAccountHeader(Pair<String,String> accountInfo) {
+    private void addAccountHeader(String accountLabel) {
         mAccountHeaderContainer.setVisibility(View.VISIBLE);
 
         // Set the account name
-        final String accountName = TextUtils.isEmpty(accountInfo.first)
-                ? accountInfo.second : accountInfo.first;
         mAccountHeaderName.setVisibility(View.VISIBLE);
-        mAccountHeaderName.setText(accountName);
+        mAccountHeaderName.setText(accountLabel);
 
         // Set the account type
         final String selectorTitle = getResources().getString(
@@ -827,13 +828,13 @@
 
         // Set the content description
         mAccountHeaderContainer.setContentDescription(
-                EditorUiUtils.getAccountInfoContentDescription(accountName, selectorTitle));
+                EditorUiUtils.getAccountInfoContentDescription(accountLabel,
+                        selectorTitle));
     }
 
-    private void addAccountSelector(Pair<String,String> accountInfo,
-            final RawContactDelta rawContactDelta) {
+    private void addAccountSelector(final RawContactDelta rawContactDelta, CharSequence nameLabel) {
         // Show save to default account.
-        addAccountHeader(accountInfo);
+        addAccountHeader(nameLabel.toString());
         // Add handlers for choosing another account to save to.
         mAccountHeaderExpanderIcon.setVisibility(View.VISIBLE);
         mAccountHeaderContainer.setOnClickListener(new View.OnClickListener() {
diff --git a/src/com/android/contacts/editor/ContactEditorBaseFragment.java b/src/com/android/contacts/editor/ContactEditorBaseFragment.java
index d8045cf..05f6c47 100644
--- a/src/com/android/contacts/editor/ContactEditorBaseFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorBaseFragment.java
@@ -58,6 +58,7 @@
 import com.android.contacts.ContactSaveService;
 import com.android.contacts.GroupMetaDataLoader;
 import com.android.contacts.R;
+import com.android.contacts.activities.CompactContactEditorActivity;
 import com.android.contacts.activities.ContactEditorAccountsChangedActivity;
 import com.android.contacts.activities.ContactEditorBaseActivity;
 import com.android.contacts.activities.ContactEditorBaseActivity.ContactEditor;
@@ -580,6 +581,9 @@
                 mHasNewContact = true;
                 if (mAccountWithDataSet != null) {
                     createContact(mAccountWithDataSet);
+                } else if (mIntentExtras != null && mIntentExtras.getBoolean(
+                        ContactEditorBaseActivity.EXTRA_SAVE_TO_DEVICE_FLAG, false)) {
+                    createContact(null);
                 } else {
                     // No Account specified. Let the user choose
                     // Load Accounts async so that we can present them
diff --git a/src/com/android/contacts/editor/ContactEditorUtils.java b/src/com/android/contacts/editor/ContactEditorUtils.java
index 1b0da05..b976456 100644
--- a/src/com/android/contacts/editor/ContactEditorUtils.java
+++ b/src/com/android/contacts/editor/ContactEditorUtils.java
@@ -113,18 +113,13 @@
      *
      * This should be called when saving a newly created contact.
      *
-     * @param defaultAccount the account used to save a newly created contact.  Or pass {@code null}
-     *     If the user selected "local only".
+     * @param defaultAccount the account used to save a newly created contact.
      */
     public void saveDefaultAndAllAccounts(AccountWithDataSet defaultAccount) {
         final SharedPreferences.Editor editor = mPrefs.edit()
                 .putBoolean(mAnythingSavedKey, true);
 
-        if (defaultAccount == null || defaultAccount.isLocalAccount()) {
-            // If the default is "local only", there should be no writable accounts.
-            // This should always be the case with our spec, but because we load the account list
-            // asynchronously using a worker thread, it is possible that there are accounts at this
-            // point. So if the default is null always clear the account list.
+        if (defaultAccount == null) {
             editor.remove(KEY_KNOWN_ACCOUNTS);
             editor.remove(mDefaultAccountKey);
         } else {
@@ -212,7 +207,9 @@
         final List<AccountWithDataSet> currentWritableAccounts = getWritableAccounts();
 
         if (currentWritableAccounts.size() == 1) {
-            return false;
+            // TODO: This will only work for devices that use a null device account but it should
+            // probably should notify for other OEM device account types as well.
+            return isFirstLaunch() && currentWritableAccounts.get(0).isLocalAccount();
         }
 
         if (isFirstLaunch()) {
@@ -226,12 +223,10 @@
             return true;
         }
 
-        // If there is an inconsistent state in the preferences file - default account is null
-        // ("local" account) while there are multiple accounts, then show the notification dialog.
-        // This shouldn't ever happen, but this should allow the user can get back into a normal
-        // state after they respond to the notification.
-        if ((defaultAccount == null || defaultAccount.isLocalAccount())
-                && currentWritableAccounts.size() > 0) {
+        // If there is an inconsistent state in the preferences file then show the notification
+        // dialog. This shouldn't ever happen, but this should allow the user can get back into a
+        // normal state after they respond to the notification.
+        if (defaultAccount == null) {
             Log.e(TAG, "Preferences file in an inconsistent state, request that the default account"
                     + " and current writable accounts be saved again");
             return true;
diff --git a/src/com/android/contacts/editor/EditorUiUtils.java b/src/com/android/contacts/editor/EditorUiUtils.java
index 8772cbb..89d830b 100644
--- a/src/com/android/contacts/editor/EditorUiUtils.java
+++ b/src/com/android/contacts/editor/EditorUiUtils.java
@@ -16,15 +16,14 @@
 
 package com.android.contacts.editor;
 
-import static android.provider.ContactsContract.CommonDataKinds.GroupMembership;
-import static android.provider.ContactsContract.CommonDataKinds.StructuredName;
-import static com.android.contacts.common.util.MaterialColorMapUtils.getDefaultPrimaryAndSecondaryColors;
-
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.drawable.Drawable;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Build;
 import android.provider.ContactsContract.CommonDataKinds.Email;
 import android.provider.ContactsContract.CommonDataKinds.Event;
 import android.provider.ContactsContract.CommonDataKinds.Im;
@@ -36,11 +35,7 @@
 import android.provider.ContactsContract.CommonDataKinds.SipAddress;
 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
 import android.provider.ContactsContract.CommonDataKinds.Website;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.os.Build;
 import android.text.TextUtils;
-import android.util.Pair;
 import android.widget.ImageView;
 
 import com.android.contacts.R;
@@ -49,18 +44,20 @@
 import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
 import com.android.contacts.common.ContactsUtils;
 import com.android.contacts.common.model.ValuesDelta;
-import com.android.contacts.common.model.account.AccountType;
-import com.android.contacts.common.model.account.GoogleAccountType;
 import com.android.contacts.common.model.dataitem.DataKind;
 import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
+import com.android.contacts.common.model.account.AccountDisplayInfo;
 import com.android.contacts.util.ContactPhotoUtils;
 import com.android.contacts.widget.QuickContactImageView;
-
 import com.google.common.collect.Maps;
 
 import java.io.FileNotFoundException;
 import java.util.HashMap;
 
+import static android.provider.ContactsContract.CommonDataKinds.GroupMembership;
+import static android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import static com.android.contacts.common.util.MaterialColorMapUtils.getDefaultPrimaryAndSecondaryColors;
+
 /**
  * Utility methods for creating contact editor.
  */
@@ -111,52 +108,34 @@
         return id;
     }
 
-    /**
-     * Returns the account name and account type labels to display for local accounts.
-     */
-    public static Pair<String,String> getLocalAccountInfo(Context context,
-            String accountName, AccountType accountType) {
-        if (TextUtils.isEmpty(accountName)) {
-            return new Pair<>(
-                    /* accountName =*/ null,
-                    context.getString(R.string.local_profile_title));
+
+    public static String getAccountHeaderLabelForMyProfile(Context context,
+            AccountDisplayInfo displayableAccount) {
+        if (displayableAccount.isDeviceAccount()) {
+            return context.getString(R.string.local_profile_title);
+        } else {
+            return context.getString(R.string.external_profile_title,
+                    displayableAccount.getTypeLabel());
         }
-        return new Pair<>(
-                accountName,
-                context.getString(R.string.external_profile_title,
-                        accountType.getDisplayLabel(context)));
     }
 
-    /**
-     * Returns the account name and account type labels to display for the given account type.
-     */
-    public static Pair<String,String> getAccountInfo(Context context, String accountName,
-            AccountType accountType) {
-        CharSequence accountTypeDisplayLabel = accountType.getDisplayLabel(context);
-        if (TextUtils.isEmpty(accountTypeDisplayLabel)
-                || TextUtils.equals(
-                        context.getString(R.string.account_phone), accountTypeDisplayLabel)) {
-            accountTypeDisplayLabel = context.getString(R.string.account_phone);
-        } else if (GoogleAccountType.ACCOUNT_TYPE.equals(accountType.accountType)
-                && accountType.dataSet == null){
-            accountTypeDisplayLabel = context.getString(R.string.google_account_type_format,
-                    accountTypeDisplayLabel);
+    public static String getAccountTypeHeaderLabel(Context context, AccountDisplayInfo
+            displayableAccount)  {
+        if (displayableAccount.isDeviceAccount()) {
+            // Do nothing. Type label should be "Device"
+            return displayableAccount.getTypeLabel().toString();
+        } else if (displayableAccount.isGoogleAccount()) {
+            return context.getString(R.string.google_account_type_format,
+                    displayableAccount.getTypeLabel());
         } else {
-            accountTypeDisplayLabel = context.getString(R.string.account_type_format,
-                    accountTypeDisplayLabel);
+            return context.getString(R.string.account_type_format,
+                    displayableAccount.getTypeLabel());
         }
-
-        if (TextUtils.isEmpty(accountName)) {
-            return new Pair<>(/* accountName */ null, accountTypeDisplayLabel.toString());
-        }
-
-        return new Pair<>(context.getString(R.string.from_account_format, accountName),
-                accountTypeDisplayLabel.toString());
     }
 
     /**
      * Returns a content description String for the container of the account information
-     * returned by {@link #getAccountInfo}.
+     * returned by {@link #getAccountTypeHeaderLabel(Context, AccountDisplayInfo)}.
      */
     public static String getAccountInfoContentDescription(CharSequence accountName,
             CharSequence accountType) {
diff --git a/src/com/android/contacts/editor/RawContactEditorView.java b/src/com/android/contacts/editor/RawContactEditorView.java
index 1e90e04..6d8c5ad 100644
--- a/src/com/android/contacts/editor/RawContactEditorView.java
+++ b/src/com/android/contacts/editor/RawContactEditorView.java
@@ -27,7 +27,6 @@
 import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.Data;
 import android.util.AttributeSet;
-import android.util.Pair;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -35,6 +34,8 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import com.android.contacts.common.model.account.AccountDisplayInfo;
+import com.android.contacts.common.model.account.AccountDisplayInfoFactory;
 import com.android.contacts.GroupMetaDataLoader;
 import com.android.contacts.R;
 import com.android.contacts.common.model.account.AccountType;
@@ -82,6 +83,8 @@
     private DataKind mGroupMembershipKind;
     private RawContactDelta mState;
 
+    private AccountDisplayInfoFactory mAccountDisplayInfoFactory;
+
     public RawContactEditorView(Context context) {
         super(context);
     }
@@ -138,6 +141,8 @@
         mAccountHeaderTypeTextView = (TextView) findViewById(R.id.account_type);
         mAccountHeaderNameTextView = (TextView) findViewById(R.id.account_name);
         mAccountIconImageView = (ImageView) findViewById(R.id.account_type_icon);
+
+        mAccountDisplayInfoFactory = AccountDisplayInfoFactory.forAllAccounts(getContext());
     }
 
     @Override
@@ -182,18 +187,28 @@
 
         mRawContactId = state.getRawContactId();
 
-        // Fill in the account info
-        final Pair<String,String> accountInfo = isProfile
-                ? EditorUiUtils.getLocalAccountInfo(getContext(), state.getAccountName(), type)
-                : EditorUiUtils.getAccountInfo(getContext(), state.getAccountName(), type);
-        if (accountInfo.first == null) {
-            // Hide this view so the other text view will be centered vertically
+        final AccountDisplayInfo account = mAccountDisplayInfoFactory
+                .getAccountDisplayInfoFor(state);
+
+        final String accountTypeLabel;
+        final String accountNameLabel;
+        if (isProfile) {
+            accountTypeLabel = EditorUiUtils.getAccountHeaderLabelForMyProfile(
+                    getContext(), account);
+            accountNameLabel = account.getNameLabel().toString();
+        } else {
+            accountTypeLabel = account.getTypeLabel().toString();
+            accountNameLabel = account.getNameLabel().toString();
+        }
+
+        if (!account.hasDistinctName()) {
+            // Hide this view so the other view will be centered vertically
             mAccountHeaderNameTextView.setVisibility(View.GONE);
         } else {
             mAccountHeaderNameTextView.setVisibility(View.VISIBLE);
-            mAccountHeaderNameTextView.setText(accountInfo.first);
+            mAccountHeaderNameTextView.setText(accountNameLabel);
         }
-        mAccountHeaderTypeTextView.setText(accountInfo.second);
+        mAccountHeaderTypeTextView.setText(accountTypeLabel);
         updateAccountHeaderContentDescription();
 
         mAccountIconImageView.setImageDrawable(state.getRawContactAccountType(getContext())
diff --git a/src/com/android/contacts/editor/RawContactReadOnlyEditorView.java b/src/com/android/contacts/editor/RawContactReadOnlyEditorView.java
index ad8504c..d55403b 100644
--- a/src/com/android/contacts/editor/RawContactReadOnlyEditorView.java
+++ b/src/com/android/contacts/editor/RawContactReadOnlyEditorView.java
@@ -27,7 +27,6 @@
 import android.provider.ContactsContract.RawContacts;
 import android.text.TextUtils;
 import android.util.AttributeSet;
-import android.util.Pair;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.View.OnClickListener;
@@ -36,6 +35,8 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import com.android.contacts.common.model.account.AccountDisplayInfo;
+import com.android.contacts.common.model.account.AccountDisplayInfoFactory;
 import com.android.contacts.R;
 import com.android.contacts.common.GeoUtil;
 import com.android.contacts.common.compat.PhoneNumberUtilsCompat;
@@ -55,6 +56,8 @@
         implements OnClickListener {
     private LayoutInflater mInflater;
 
+    private AccountDisplayInfoFactory mAccountDisplayInfoFactory;
+
     private TextView mName;
     private Button mEditExternallyButton;
     private ViewGroup mGeneral;
@@ -93,6 +96,8 @@
         mAccountHeaderTypeTextView = (TextView) findViewById(R.id.account_type);
         mAccountHeaderNameTextView = (TextView) findViewById(R.id.account_name);
         mAccountIconImageView = (ImageView) findViewById(R.id.account_type_icon);
+
+        mAccountDisplayInfoFactory = AccountDisplayInfoFactory.forAllAccounts(getContext());
     }
 
     /**
@@ -117,17 +122,29 @@
         mAccountType = state.getAccountType();
         mDataSet = state.getDataSet();
 
-        final Pair<String,String> accountInfo = isProfile
-                ? EditorUiUtils.getLocalAccountInfo(getContext(), state.getAccountName(), type)
-                : EditorUiUtils.getAccountInfo(getContext(), state.getAccountName(), type);
-        if (accountInfo.first == null) {
-            // Hide this view so the other text view will be centered vertically
+
+        final AccountDisplayInfo account = mAccountDisplayInfoFactory
+                .getAccountDisplayInfoFor(state);
+
+        final String accountTypeLabel;
+        final String accountNameLabel;
+        if (isProfile) {
+            accountTypeLabel = EditorUiUtils.getAccountHeaderLabelForMyProfile(
+                    getContext(), account);
+            accountNameLabel = account.getNameLabel().toString();
+        } else {
+            accountTypeLabel = account.getTypeLabel().toString();
+            accountNameLabel = account.getNameLabel().toString();
+        }
+
+        if (!account.hasDistinctName()) {
+            // Hide this view so the other view will be centered vertically
             mAccountHeaderNameTextView.setVisibility(View.GONE);
         } else {
             mAccountHeaderNameTextView.setVisibility(View.VISIBLE);
-            mAccountHeaderNameTextView.setText(accountInfo.first);
+            mAccountHeaderNameTextView.setText(accountNameLabel);
         }
-        mAccountHeaderTypeTextView.setText(accountInfo.second);
+        mAccountHeaderTypeTextView.setText(accountTypeLabel);
         updateAccountHeaderContentDescription();
 
         mAccountIconImageView.setImageDrawable(state.getRawContactAccountType(getContext())
diff --git a/src/com/android/contacts/group/GroupNameEditDialogFragment.java b/src/com/android/contacts/group/GroupNameEditDialogFragment.java
index 36a4710..544dd87 100644
--- a/src/com/android/contacts/group/GroupNameEditDialogFragment.java
+++ b/src/com/android/contacts/group/GroupNameEditDialogFragment.java
@@ -245,8 +245,7 @@
         final String callbackAction = getArguments().getString(ARG_CALLBACK_ACTION);
         final Intent serviceIntent;
         if (mIsInsert) {
-            serviceIntent = ContactSaveService.createNewGroupIntent(getActivity(),
-                    new AccountWithDataSet(mAccount.name, mAccount.type, mAccount.dataSet),
+            serviceIntent = ContactSaveService.createNewGroupIntent(getActivity(), mAccount,
                     name, null, getActivity().getClass(), callbackAction);
         } else {
             serviceIntent = ContactSaveService.createGroupRenameIntent(getActivity(), mGroupId,
diff --git a/src/com/android/contacts/interactions/AccountFiltersFragment.java b/src/com/android/contacts/interactions/AccountFiltersFragment.java
index b2b21f7..8d92327 100644
--- a/src/com/android/contacts/interactions/AccountFiltersFragment.java
+++ b/src/com/android/contacts/interactions/AccountFiltersFragment.java
@@ -19,16 +19,11 @@
 import android.app.Fragment;
 import android.app.LoaderManager;
 import android.content.Loader;
-import android.database.Cursor;
 import android.os.Bundle;
 
 import com.android.contacts.common.list.ContactListFilter;
 import com.android.contacts.common.util.AccountFilterUtil;
-import com.android.contacts.common.util.DeviceLocalContactsFilterProvider;
-import com.android.contactsbind.ObjectFactory;
 
-import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 
 /**
@@ -37,7 +32,6 @@
 public class AccountFiltersFragment extends Fragment {
 
     private static final int LOADER_FILTERS = 1;
-    private static final int LOADER_DEVICE_LOCAL_CONTACTS = 3;
 
     /**
      * Callbacks for hosts of the {@link AccountFiltersFragment}.
@@ -50,8 +44,6 @@
         void onFiltersLoaded(List<ContactListFilter> accountFilterItems);
     }
 
-    private LoaderManager.LoaderCallbacks<Cursor> mDeviceLocalLoaderListener;
-
     private final LoaderManager.LoaderCallbacks<List<ContactListFilter>> mFiltersLoaderListener =
             new LoaderManager.LoaderCallbacks<List<ContactListFilter>> () {
                 @Override
@@ -62,43 +54,25 @@
                 @Override
                 public void onLoadFinished(
                         Loader<List<ContactListFilter>> loader, List<ContactListFilter> data) {
-                    if (data == null) {
-                        mLoadedFilters = Collections.emptyList();
-                    } else {
-                        mLoadedFilters = data;
+                    if (mListener != null && data != null) {
+                        mListener.onFiltersLoaded(data);
                     }
-                    notifyWithCurrentFilters();
                 }
 
                 public void onLoaderReset(Loader<List<ContactListFilter>> loader) {
                 }
             };
 
-
-    private List<ContactListFilter> mLoadedFilters = null;
-    private List<ContactListFilter> mDeviceLocalFilters = null;
     private AccountFiltersListener mListener;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-
-        mDeviceLocalLoaderListener = new DeviceLocalContactsFilterProvider(getActivity(),
-                ObjectFactory.getDeviceAccountFilter(getActivity())) {
-            @Override
-            public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
-                super.onLoadFinished(loader, data);
-                mDeviceLocalFilters = getListFilters();
-                notifyWithCurrentFilters();
-            }
-        };
     }
 
     @Override
     public void onStart() {
         getLoaderManager().initLoader(LOADER_FILTERS, null, mFiltersLoaderListener);
-        getLoaderManager().initLoader(LOADER_DEVICE_LOCAL_CONTACTS, null,
-                mDeviceLocalLoaderListener);
 
         super.onStart();
     }
@@ -106,12 +80,4 @@
     public void setListener(AccountFiltersListener listener) {
         mListener = listener;
     }
-
-    private void notifyWithCurrentFilters() {
-        if (mListener == null || mLoadedFilters == null || mDeviceLocalFilters == null) return;
-
-        final List<ContactListFilter> result = new ArrayList<>(mLoadedFilters);
-        result.addAll(mDeviceLocalFilters);
-        mListener.onFiltersLoaded(result);
-    }
 }