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/tests/src/com/android/contacts/common/model/AccountWithDataSetTest.java b/tests/src/com/android/contacts/common/model/AccountWithDataSetTest.java
index e28f09e..7bfb922 100644
--- a/tests/src/com/android/contacts/common/model/AccountWithDataSetTest.java
+++ b/tests/src/com/android/contacts/common/model/AccountWithDataSetTest.java
@@ -58,6 +58,14 @@
         MoreAsserts.assertNotEqual(a3, a2r);
     }
 
+    public void testStringifyAndUnstringifyLocalAccount() {
+        final String stringified = AccountWithDataSet.getLocalAccount().stringify();
+
+        final AccountWithDataSet restored = AccountWithDataSet.unstringify(stringified);
+
+        assertEquals(AccountWithDataSet.getLocalAccount(), restored);
+    }
+
     public void testStringifyListAndUnstringify() {
         AccountWithDataSet a1 = new AccountWithDataSet("name1", "typeA", null);
         AccountWithDataSet a2 = new AccountWithDataSet("name2", "typeB", null);
diff --git a/tests/src/com/android/contacts/common/model/DeviceLocalAccountLocatorTests.java b/tests/src/com/android/contacts/common/model/DeviceLocalAccountLocatorTests.java
new file mode 100644
index 0000000..e03c3e5
--- /dev/null
+++ b/tests/src/com/android/contacts/common/model/DeviceLocalAccountLocatorTests.java
@@ -0,0 +1,162 @@
+/*
+ * 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.content.ContentResolver;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.os.CancellationSignal;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.RawContacts;
+import android.support.annotation.Nullable;
+import android.test.AndroidTestCase;
+import android.test.mock.MockContentResolver;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.contacts.common.model.DeviceLocalAccountLocator;
+import com.android.contacts.common.model.account.AccountWithDataSet;
+import com.android.contacts.common.test.mocks.MockContentProvider;
+import com.android.contacts.common.util.DeviceLocalAccountTypeFactory;
+import com.android.contacts.tests.FakeDeviceAccountTypeFactory;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+@SmallTest
+public class DeviceLocalAccountLocatorTests extends AndroidTestCase {
+
+    // Basic smoke test that just checks that it doesn't throw when loading from CP2. We don't
+    // care what CP2 actually contains for this.
+    public void testShouldNotCrash() {
+        final DeviceLocalAccountLocator sut = new DeviceLocalAccountLocator(
+                getContext().getContentResolver(),
+                new DeviceLocalAccountTypeFactory.Default(getContext()),
+                Collections.<AccountWithDataSet>emptyList());
+        sut.getDeviceLocalAccounts();
+        // We didn't throw so it passed
+    }
+
+    public void test_getDeviceLocalAccounts_returnsEmptyListWhenNoRawContactsHaveDeviceType() {
+        final DeviceLocalAccountLocator sut = createWithQueryResult(queryResult(
+                        "user", "com.example",
+                        "user", "com.example",
+                        "user", "com.example"));
+        assertTrue(sut.getDeviceLocalAccounts().isEmpty());
+    }
+
+    public void test_getDeviceLocalAccounts_returnsListWithItemForNullAccount() {
+        final DeviceLocalAccountLocator sut = createWithQueryResult(queryResult(
+                "user", "com.example",
+                null, null,
+                "user", "com.example",
+                null, null));
+
+        assertEquals(1, sut.getDeviceLocalAccounts().size());
+    }
+
+    public void test_getDeviceLocalAccounts_containsItemForEachDeviceAccount() {
+        final DeviceLocalAccountTypeFactory stubFactory = new FakeDeviceAccountTypeFactory()
+                .withDeviceTypes(null, "vnd.sec.contact.phone")
+                .withSimTypes("vnd.sec.contact.sim");
+        final DeviceLocalAccountLocator sut = new DeviceLocalAccountLocator(
+                createStubResolverWithContentQueryResult(queryResult(
+                        "user", "com.example",
+                        "user", "com.example",
+                        "phone_account", "vnd.sec.contact.phone",
+                        null, null,
+                        "phone_account", "vnd.sec.contact.phone",
+                        "user", "com.example",
+                        null, null,
+                        "sim_account", "vnd.sec.contact.sim",
+                        "sim_account_2", "vnd.sec.contact.sim"
+                )), stubFactory,
+                Collections.<AccountWithDataSet>emptyList());
+
+        assertEquals(4, sut.getDeviceLocalAccounts().size());
+    }
+
+    public void test_getDeviceLocalAccounts_doesNotContainItemsForKnownAccounts() {
+        final DeviceLocalAccountLocator sut = new DeviceLocalAccountLocator(
+                getContext().getContentResolver(), new FakeDeviceAccountTypeFactory(),
+                Arrays.asList(new AccountWithDataSet("user", "com.example", null),
+                        new AccountWithDataSet("user1", "com.example", null),
+                        new AccountWithDataSet("user", "com.example.1", null)));
+
+        assertTrue("Selection should filter known accounts", sut.getSelection().contains("NOT IN (?,?)"));
+
+        final List<String> args = Arrays.asList(sut.getSelectionArgs());
+        assertEquals(2, args.size());
+        assertTrue("Selection args is missing an expected value", args.contains("com.example"));
+        assertTrue("Selection args is missing an expected value", args.contains("com.example.1"));
+    }
+
+    private DeviceLocalAccountLocator createWithQueryResult(
+            Cursor cursor) {
+        final DeviceLocalAccountLocator locator = new DeviceLocalAccountLocator(
+                createStubResolverWithContentQueryResult(cursor),
+                new DeviceLocalAccountTypeFactory.Default(getContext()),
+                Collections.<AccountWithDataSet>emptyList());
+        return locator;
+    }
+
+
+    private ContentResolver createStubResolverWithContentQueryResult(Cursor cursor) {
+        final MockContentResolver resolver = new MockContentResolver();
+        resolver.addProvider(ContactsContract.AUTHORITY, new FakeContactsProvider(cursor));
+        return resolver;
+    }
+
+    private Cursor queryResult(String... nameTypePairs) {
+        final MatrixCursor cursor = new MatrixCursor(new String[]
+                { RawContacts.ACCOUNT_NAME, RawContacts.ACCOUNT_TYPE, RawContacts.DATA_SET });
+        for (int i = 0; i < nameTypePairs.length; i+=2) {
+            cursor.newRow().add(nameTypePairs[i]).add(nameTypePairs[i+1])
+                    .add(null);
+        }
+        return cursor;
+    }
+
+    private static class FakeContactsProvider extends MockContentProvider {
+        public Cursor mNextQueryResult;
+
+        public FakeContactsProvider() {}
+
+        public FakeContactsProvider(Cursor result) {
+            mNextQueryResult = result;
+        }
+
+        public void setNextQueryResult(Cursor result) {
+            mNextQueryResult = result;
+        }
+
+        @Override
+        public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+                String sortOrder) {
+            return query(uri, projection, selection, selectionArgs, sortOrder, null);
+        }
+
+        @Nullable
+        @Override
+        public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+                String sortOrder, CancellationSignal cancellationSignal) {
+            return mNextQueryResult;
+        }
+    }
+}
diff --git a/tests/src/com/android/contacts/common/preference/ContactsPreferencesTest.java b/tests/src/com/android/contacts/common/preference/ContactsPreferencesTest.java
index 26e811d..bbbd65c 100644
--- a/tests/src/com/android/contacts/common/preference/ContactsPreferencesTest.java
+++ b/tests/src/com/android/contacts/common/preference/ContactsPreferencesTest.java
@@ -142,9 +142,11 @@
                 .thenReturn(new AccountWithDataSet("name1", "type1", "dataset1").stringify(),
                         new AccountWithDataSet("name2", "type2", "dataset2").stringify());
 
-        Assert.assertEquals("name1", mContactsPreferences.getDefaultAccount());
+        Assert.assertEquals(new AccountWithDataSet("name1", "type1", "dataset1"),
+                mContactsPreferences.getDefaultAccount());
         mContactsPreferences.refreshValue(ACCOUNT_KEY);
 
-        Assert.assertEquals("name2", mContactsPreferences.getDefaultAccount());
+        Assert.assertEquals(new AccountWithDataSet("name2", "type2", "dataset2"),
+                mContactsPreferences.getDefaultAccount());
     }
 }
diff --git a/tests/src/com/android/contacts/common/util/DeviceLocalContactsFilterProviderTests.java b/tests/src/com/android/contacts/common/util/DeviceLocalContactsFilterProviderTests.java
deleted file mode 100644
index d776ab8..0000000
--- a/tests/src/com/android/contacts/common/util/DeviceLocalContactsFilterProviderTests.java
+++ /dev/null
@@ -1,221 +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.ContentProvider;
-import android.content.Context;
-import android.content.CursorLoader;
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.net.Uri;
-import android.os.CancellationSignal;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.RawContacts;
-import android.support.annotation.Nullable;
-import android.test.LoaderTestCase;
-import android.test.mock.MockContentResolver;
-import android.test.mock.MockContext;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import com.android.contacts.common.list.ContactListFilter;
-import com.android.contacts.common.test.mocks.MockContentProvider;
-
-import org.mockito.Mockito;
-
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-
-import static org.mockito.Mockito.when;
-
-@SmallTest
-public class DeviceLocalContactsFilterProviderTests extends LoaderTestCase {
-
-    // Basic smoke test that just checks that it doesn't throw when loading from CP2. We don't
-    // care what CP2 actually contains for this.
-    public void testShouldNotCrash() {
-        final DeviceLocalContactsFilterProvider sut = new DeviceLocalContactsFilterProvider(
-                getContext(), DeviceAccountFilter.ONLY_NULL);
-        final CursorLoader loader = sut.onCreateLoader(0, null);
-        getLoaderResultSynchronously(loader);
-        // We didn't throw so it passed
-    }
-
-    public void testCreatesNoFiltersIfNoRawContactsHaveDeviceAccountType() {
-        final DeviceLocalContactsFilterProvider sut = createWithFilterAndLoaderResult(
-                DeviceAccountFilter.ONLY_NULL, queryResult(
-                        "user", "com.example",
-                        "user", "com.example",
-                        "user", "com.example"));
-        sut.setKnownAccountTypes("com.example");
-
-        doLoad(sut);
-
-        assertEquals(0, sut.getListFilters().size());
-    }
-
-    public void testCreatesOneFilterForDeviceAccount() {
-        final DeviceLocalContactsFilterProvider sut = createWithFilterAndLoaderResult(
-                DeviceAccountFilter.ONLY_NULL, queryResult(
-                        "user", "com.example",
-                        "user", "com.example",
-                        null, null,
-                        "user", "com.example",
-                        null, null));
-        sut.setKnownAccountTypes("com.example");
-
-        doLoad(sut);
-
-        assertEquals(1, sut.getListFilters().size());
-        assertEquals(ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS,
-                sut.getListFilters().get(0).filterType);
-    }
-
-    public void testCreatesOneFilterForEachDeviceAccount() {
-         final DeviceLocalContactsFilterProvider sut = createWithFilterAndLoaderResult(
-                 filterAllowing(null, "vnd.sec.contact.phone", "vnd.sec.contact.sim"), queryResult(
-                         "sim_account", "vnd.sec.contact.sim",
-                         "user", "com.example",
-                         "user", "com.example",
-                         "phone_account", "vnd.sec.contact.phone",
-                         null, null,
-                         "phone_account", "vnd.sec.contact.phone",
-                         "user", "com.example",
-                         null, null,
-                         "sim_account", "vnd.sec.contact.sim",
-                         "sim_account_2", "vnd.sec.contact.sim"
-                 ));
-        sut.setKnownAccountTypes("com.example");
-
-        doLoad(sut);
-
-        assertEquals(4, sut.getListFilters().size());
-    }
-
-    public void testFilterIsUpdatedWhenLoaderReloads() {
-        final FakeContactsProvider provider = new FakeContactsProvider();
-        final DeviceLocalContactsFilterProvider sut = new DeviceLocalContactsFilterProvider(
-                createStubContextWithContactsProvider(provider), DeviceAccountFilter.ONLY_NULL);
-        sut.setKnownAccountTypes("com.example");
-
-        provider.setNextQueryResult(queryResult(
-                null, null,
-                "user", "com.example",
-                "user", "com.example"
-        ));
-        doLoad(sut);
-
-        assertFalse(sut.getListFilters().isEmpty());
-
-        provider.setNextQueryResult(queryResult(
-                "user", "com.example",
-                "user", "com.example"
-        ));
-        doLoad(sut);
-
-        assertTrue(sut.getListFilters().isEmpty());
-    }
-
-    public void testDoesNotCreateFiltersForKnownAccounts() {
-        final DeviceLocalContactsFilterProvider sut = new DeviceLocalContactsFilterProvider(
-                getContext(), DeviceAccountFilter.ONLY_NULL);
-        sut.setKnownAccountTypes("com.example", "maybe_syncable_device_account_type");
-
-        final CursorLoader loader = sut.onCreateLoader(0, null);
-
-        // The filtering is done at the DB level rather than in the code so just verify that
-        // selection is about right.
-        assertTrue("Loader selection is wrong", loader.getSelection().contains("NOT IN (?,?)"));
-        assertEquals("com.example", loader.getSelectionArgs()[0]);
-        assertEquals("maybe_syncable_device_account_type", loader.getSelectionArgs()[1]);
-    }
-
-    private void doLoad(DeviceLocalContactsFilterProvider loaderCallbacks) {
-        final CursorLoader loader = loaderCallbacks.onCreateLoader(0, null);
-        final Cursor cursor = getLoaderResultSynchronously(loader);
-        loaderCallbacks.onLoadFinished(loader, cursor);
-    }
-
-    private DeviceLocalContactsFilterProvider createWithFilterAndLoaderResult(
-            DeviceAccountFilter filter, Cursor cursor) {
-        final DeviceLocalContactsFilterProvider result = new DeviceLocalContactsFilterProvider(
-                createStubContextWithContentQueryResult(cursor), filter);
-        return result;
-    }
-
-    private Context createStubContextWithContentQueryResult(final Cursor cursor) {
-        return createStubContextWithContactsProvider(new FakeContactsProvider(cursor));
-    }
-
-    private Context createStubContextWithContactsProvider(ContentProvider contactsProvider) {
-        final MockContentResolver resolver = new MockContentResolver();
-        resolver.addProvider(ContactsContract.AUTHORITY, contactsProvider);
-
-        final Context context = Mockito.mock(MockContext.class);
-        when(context.getContentResolver()).thenReturn(resolver);
-
-        // The loader pulls out the application context instead of usign the context directly
-        when(context.getApplicationContext()).thenReturn(context);
-
-        return context;
-    }
-
-    private Cursor queryResult(String... typeNamePairs) {
-        final MatrixCursor cursor = new MatrixCursor(new String[]
-                { RawContacts.ACCOUNT_NAME, RawContacts.ACCOUNT_TYPE });
-        for (int i = 0; i < typeNamePairs.length; i += 2) {
-            cursor.newRow().add(typeNamePairs[i]).add(typeNamePairs[i+1]);
-        }
-        return cursor;
-    }
-
-    private DeviceAccountFilter filterAllowing(String... accountTypes) {
-        final Set<String> allowed = new HashSet<>(Arrays.asList(accountTypes));
-        return new DeviceAccountFilter() {
-            @Override
-            public boolean isDeviceAccountType(String accountType) {
-                return allowed.contains(accountType);
-            }
-        };
-    }
-
-    private static class FakeContactsProvider extends MockContentProvider {
-        public Cursor mNextQueryResult;
-
-        public FakeContactsProvider() {}
-
-        public FakeContactsProvider(Cursor result) {
-            mNextQueryResult = result;
-        }
-
-        public void setNextQueryResult(Cursor result) {
-            mNextQueryResult = result;
-        }
-
-        @Override
-        public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
-                String sortOrder) {
-            return query(uri, projection, selection, selectionArgs, sortOrder, null);
-        }
-
-        @Nullable
-        @Override
-        public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
-                String sortOrder, CancellationSignal cancellationSignal) {
-            return mNextQueryResult;
-        }
-    }
-}
diff --git a/tests/src/com/android/contacts/editor/ContactEditorUtilsTest.java b/tests/src/com/android/contacts/editor/ContactEditorUtilsTest.java
index 2e0306a..b5df8c9 100644
--- a/tests/src/com/android/contacts/editor/ContactEditorUtilsTest.java
+++ b/tests/src/com/android/contacts/editor/ContactEditorUtilsTest.java
@@ -272,7 +272,7 @@
         assertTrue(mTarget.shouldShowAccountChangedNotification());
 
         // We show the notification here, and user clicked "keep local" and saved an contact.
-        mTarget.saveDefaultAndAllAccounts(null);
+        mTarget.saveDefaultAndAllAccounts(AccountWithDataSet.getLocalAccount());
 
         // Now there are no accounts, and default account is null.
 
diff --git a/tests/src/com/android/contacts/editor/EditorUiUtilsTest.java b/tests/src/com/android/contacts/editor/EditorUiUtilsTest.java
index 19f28d3..e68511f 100644
--- a/tests/src/com/android/contacts/editor/EditorUiUtilsTest.java
+++ b/tests/src/com/android/contacts/editor/EditorUiUtilsTest.java
@@ -16,10 +16,6 @@
 
 package com.android.contacts.editor;
 
-import com.android.contacts.R;
-import com.android.contacts.common.model.account.AccountType;
-import com.android.contacts.common.model.account.GoogleAccountType;
-
 import android.content.Context;
 import android.media.RingtoneManager;
 import android.net.Uri;
@@ -27,7 +23,11 @@
 import android.provider.Settings;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Pair;
+
+import com.android.contacts.common.model.account.AccountDisplayInfo;
+import com.android.contacts.R;
+import com.android.contacts.common.model.account.AccountType;
+import com.android.contacts.common.model.account.AccountWithDataSet;
 
 /**
  * Tests {@link EditorUiUtils}.
@@ -43,6 +43,11 @@
 
     private static final String RINGTONE = "content://media/external/audio/media/31";
 
+    private static final AccountWithDataSet ACCOUNT =
+            new AccountWithDataSet(ACCOUNT_NAME, "some.account.type", null);
+    private static final AccountWithDataSet GOOGLE_ACCOUNT =
+            new AccountWithDataSet(ACCOUNT_NAME, "com.google", null);
+
     private static final class MockAccountType extends AccountType {
 
         private final String mDisplayLabel;
@@ -67,86 +72,65 @@
         }
     }
 
-    public void testGetProfileAccountInfo_AccountName() {
-        final Pair pair = EditorUiUtils.getLocalAccountInfo(getContext(),
-                ACCOUNT_NAME, new MockAccountType(DISPLAY_LABEL));
+    public void testGetProfileAccountInfo_NonLocalAccount() {
+        final AccountDisplayInfo account = new AccountDisplayInfo(ACCOUNT, ACCOUNT_NAME,
+                DISPLAY_LABEL, /*icon*/ null, /*isDeviceAccount*/ false);
 
-        assertNotNull(pair);
-        assertEquals(ACCOUNT_NAME, pair.first);
-        assertEquals(getContext().getString(R.string.external_profile_title, DISPLAY_LABEL),
-                pair.second); // My LunkedIn profile
+        final String label = EditorUiUtils.getAccountHeaderLabelForMyProfile(getContext(),
+                account);
+
+        // My LunkedIn profile
+        final String expected = getContext()
+                .getString(R.string.external_profile_title, DISPLAY_LABEL);
+        assertEquals(expected, label);
     }
 
-    public void testGetProfileAccountInfo_NoAccountName() {
-        final Pair pair = EditorUiUtils.getLocalAccountInfo(getContext(),
-                /* accountName =*/ null, new MockAccountType(DISPLAY_LABEL));
 
-        assertNotNull(pair);
-        assertNull(pair.first);
-        assertEquals(getContext().getString(R.string.local_profile_title),
-                pair.second); // "My local profile
+    public void testGetProfileAccountInfo_DeviceLocalAccount() {
+        final AccountDisplayInfo account = new AccountDisplayInfo(ACCOUNT, "Device",
+                "Device", null, true);
+
+        final String label = EditorUiUtils.getAccountHeaderLabelForMyProfile(getContext(),
+                account);
+
+        // "My local profile"
+        final String expected = getContext().getString(R.string.local_profile_title);
+        assertEquals(expected, label);
     }
 
-    public void testGetAccountInfo_AccountName_DisplayLabel() {
-        final Pair pair = EditorUiUtils.getAccountInfo(getContext(),
-                ACCOUNT_NAME, new MockAccountType(DISPLAY_LABEL));
+    public void testGetAccountInfo_AccountType_NonGoogle() {
+        final AccountDisplayInfo account = new AccountDisplayInfo(ACCOUNT, ACCOUNT_NAME,
+                DISPLAY_LABEL, /*icon*/ null, /*isDeviceAccount*/ false);
 
-        assertNotNull(pair);
-        assertEquals(getContext().getString(R.string.from_account_format, ACCOUNT_NAME),
-                pair.first); // somebody@lunkedin.com
-        assertEquals(getContext().getString(R.string.account_type_format, DISPLAY_LABEL),
-                pair.second); // LunkedIn Contact
+        final String label = EditorUiUtils.getAccountTypeHeaderLabel(getContext(), account);
+
+        // LunkedIn Contact
+        final String expected = getContext().getString(R.string.account_type_format, DISPLAY_LABEL);
+        assertEquals(expected, label);
     }
 
-    public void testGetAccountInfo_AccountName_DisplayLabel_GoogleAccountType() {
-        final AccountType accountType = new MockAccountType(GOOGLE_DISPLAY_LABEL);
-        accountType.accountType = GoogleAccountType.ACCOUNT_TYPE;
-        final Pair pair = EditorUiUtils.getAccountInfo(getContext(),
-                GOOGLE_ACCOUNT_NAME, accountType);
+    public void testGetAccountInfo_AccountType_Google() {
+        final AccountDisplayInfo account = new AccountDisplayInfo(GOOGLE_ACCOUNT, ACCOUNT_NAME,
+                GOOGLE_DISPLAY_LABEL, /*icon*/ null, /*isDeviceAccount*/ false);
 
-        assertNotNull(pair);
-        assertEquals(getContext().getString(R.string.from_account_format, GOOGLE_ACCOUNT_NAME),
-                pair.first); // somebody@gmail.com
-        assertEquals(
-                getContext().getString(R.string.google_account_type_format, GOOGLE_DISPLAY_LABEL),
-                pair.second); // Google Account
+        final String label = EditorUiUtils.getAccountTypeHeaderLabel(getContext(), account);
+
+        // Google Account
+        final String expected = getContext().getString(R.string.google_account_type_format,
+                GOOGLE_DISPLAY_LABEL);
+        assertEquals(expected, label);
     }
 
-    public void testGetAccountInfo_AccountName_NoDisplayLabel() {
-        final Pair pair = EditorUiUtils.getAccountInfo(getContext(),
-                ACCOUNT_NAME, new MockAccountType(/* displayLabel =*/ null));
+  public void testGetAccountInfo_AccountType_DeviceAccount() {
+      final AccountWithDataSet deviceAccount = AccountWithDataSet.getLocalAccount();
+      final AccountDisplayInfo account = new AccountDisplayInfo(deviceAccount, "Device",
+              "Device", /*icon*/ null, /*isDeviceAccount*/ true);
 
-        assertNotNull(pair);
-        assertEquals(getContext().getString(R.string.from_account_format, ACCOUNT_NAME),
-                pair.first); // somebody@lunkedin.com
-        assertEquals(getContext().getString(R.string.account_phone), pair.second); // Device
-    }
+      final String label = EditorUiUtils.getAccountTypeHeaderLabel(getContext(), account);
 
-    public void testGetAccountInfo_NoAccountName_DisplayLabel() {
-        final Pair pair = EditorUiUtils.getAccountInfo(getContext(),
-                /* accountName =*/ null, new MockAccountType(DISPLAY_LABEL));
-
-        assertNotNull(pair);
-        assertNull(pair.first);
-        assertEquals(getContext().getString(R.string.account_type_format, DISPLAY_LABEL),
-                pair.second); // LunkedIn contact
-
-        final Pair pairDevice = EditorUiUtils.getAccountInfo(
-                getContext(),
-                /* accountName =*/ null,
-                new MockAccountType(getContext().getString(R.string.account_phone)));
-        assertNotNull(pairDevice);
-        assertNull(pairDevice.first);
-        assertEquals(getContext().getString(R.string.account_phone), pairDevice.second); // Device
-    }
-
-    public void testGetAccountInfo_NoAccountName_NoDisplayLabel() {
-        final Pair pair = EditorUiUtils.getAccountInfo(getContext(),
-                /* accountName =*/ null, new MockAccountType(/* displayLabel =*/ null));
-
-        assertNotNull(pair);
-        assertNull(pair.first);
-        assertEquals(getContext().getString(R.string.account_phone), pair.second); // Device
+      // "Device"
+      final String expected = getContext().getString(R.string.account_phone);
+      assertEquals(expected, label);
     }
 
     public void testGetRingtongStrFromUri_lessThanOrEqualsToM() {
@@ -185,4 +169,8 @@
                 currentVersion));
     }
 
+    private AccountDisplayInfo createDisplayableAccount() {
+        return new AccountDisplayInfo(ACCOUNT, ACCOUNT_NAME, DISPLAY_LABEL, null, false);
+    }
+
 }
diff --git a/tests/src/com/android/contacts/tests/FakeAccountType.java b/tests/src/com/android/contacts/tests/FakeAccountType.java
new file mode 100644
index 0000000..59327d8
--- /dev/null
+++ b/tests/src/com/android/contacts/tests/FakeAccountType.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.contacts.tests;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
+
+import com.android.contacts.common.model.account.AccountType;
+import com.android.contacts.common.model.account.AccountWithDataSet;
+
+public class FakeAccountType extends AccountType {
+    public boolean areContactsWritable = false;
+    public boolean isGroupMembershipEditable = false;
+    public String displayLabel = "The Default Label";
+    public Drawable displayIcon = new Drawable() {
+        @Override
+        public void draw(Canvas canvas) {
+        }
+
+        @Override
+        public void setAlpha(int alpha) {
+        }
+
+        @Override
+        public void setColorFilter(ColorFilter colorFilter) {
+        }
+
+        @Override
+        public int getOpacity() {
+            return PixelFormat.OPAQUE;
+        }
+    };
+
+    public FakeAccountType() {
+    }
+
+    public FakeAccountType(String type) {
+        accountType = type;
+    }
+
+    @Override
+    public Drawable getDisplayIcon(Context context) {
+        return displayIcon;
+    }
+
+    @Override
+    public String getDisplayLabel(Context context) {
+        return displayLabel;
+    }
+
+    @Override
+    public boolean areContactsWritable() {
+        return areContactsWritable;
+    }
+
+    @Override
+    public boolean isGroupMembershipEditable() {
+        return isGroupMembershipEditable;
+    }
+
+    public static FakeAccountType create(String accountType, String label) {
+        final FakeAccountType result = new FakeAccountType();
+        result.accountType = accountType;
+        result.displayLabel = label;
+        return result;
+    }
+
+    public static FakeAccountType create(String accountType, String label, Drawable icon) {
+        final FakeAccountType result = new FakeAccountType();
+        result.accountType = accountType;
+        result.displayIcon = icon;
+        result.displayLabel = label;
+        return result;
+    }
+
+    public static AccountType create(AccountWithDataSet account, String label, Drawable icon) {
+        final FakeAccountType result = create(account.type, label, icon);
+        result.accountType = account.type;
+        result.dataSet = account.dataSet;
+        return result;
+    }
+}
diff --git a/tests/src/com/android/contacts/tests/FakeDeviceAccountTypeFactory.java b/tests/src/com/android/contacts/tests/FakeDeviceAccountTypeFactory.java
new file mode 100644
index 0000000..6a500ec
--- /dev/null
+++ b/tests/src/com/android/contacts/tests/FakeDeviceAccountTypeFactory.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.contacts.tests;
+
+import com.android.contacts.common.model.account.AccountType;
+import com.android.contacts.common.model.account.FallbackAccountType;
+import com.android.contacts.common.util.DeviceLocalAccountTypeFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class FakeDeviceAccountTypeFactory implements DeviceLocalAccountTypeFactory {
+
+    private final Map<String, AccountType> mDeviceAccountTypes = new HashMap<>();
+    private final Map<String, AccountType> mSimAccountTypes = new HashMap<>();
+
+    @Override
+    public int classifyAccount(String accountType) {
+        if (mDeviceAccountTypes.containsKey(accountType)) {
+            return TYPE_DEVICE;
+        } else if (mSimAccountTypes.containsKey(accountType)) {
+            return TYPE_SIM;
+        } else {
+            return TYPE_OTHER;
+        }
+    }
+
+    @Override
+    public AccountType getAccountType(String accountType) {
+        final AccountType type = mDeviceAccountTypes.get(accountType);
+        return type == null ? mSimAccountTypes.get(accountType) : type;
+    }
+
+    public FakeDeviceAccountTypeFactory withSimTypes(String... types) {
+        for (String type : types) {
+            mSimAccountTypes.put(type, new FakeAccountType(type));
+        }
+        return this;
+    }
+
+    public FakeDeviceAccountTypeFactory withSimTypes(AccountType... types) {
+        for (AccountType type : types) {
+            mSimAccountTypes.put(type.accountType, type);
+        }
+        return this;
+    }
+
+    public FakeDeviceAccountTypeFactory withDeviceTypes(String... types) {
+        for (String type : types) {
+            mDeviceAccountTypes.put(type, new FakeAccountType(type));
+        }
+        return this;
+    }
+
+    public FakeDeviceAccountTypeFactory withDeviceTypes(AccountType... types) {
+        for (AccountType type : types) {
+            mDeviceAccountTypes.put(type.accountType, type);
+        }
+        return this;
+    }
+}
diff --git a/tests/src/com/android/contacts/util/AccountDisplayInfoFactoryTests.java b/tests/src/com/android/contacts/util/AccountDisplayInfoFactoryTests.java
new file mode 100644
index 0000000..875f40e
--- /dev/null
+++ b/tests/src/com/android/contacts/util/AccountDisplayInfoFactoryTests.java
@@ -0,0 +1,205 @@
+/*
+ * 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.util;
+
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.contacts.common.model.AccountTypeManager;
+import com.android.contacts.common.model.account.AccountDisplayInfo;
+import com.android.contacts.common.model.account.AccountDisplayInfoFactory;
+import com.android.contacts.common.model.account.AccountType;
+import com.android.contacts.common.model.account.AccountWithDataSet;
+import com.android.contacts.common.test.mocks.MockAccountTypeManager;
+import com.android.contacts.common.util.DeviceLocalAccountTypeFactory;
+import com.android.contacts.tests.FakeAccountType;
+import com.android.contacts.tests.FakeDeviceAccountTypeFactory;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+@SmallTest
+public class AccountDisplayInfoFactoryTests extends AndroidTestCase {
+
+    private Map<AccountWithDataSet, AccountType> mKnownTypes;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mKnownTypes = new HashMap<>();
+    }
+
+    public void test_displayableAccount_hasIconFromAccountType() {
+        final Drawable comExampleIcon = someDrawable();
+
+        addTypeMapping(account("user", "com.example"), "title", comExampleIcon);
+        addTypeMapping(account(null, null), "device", someDrawable());
+        addTypeMapping(account("foo", "bar.type"), "bar", someDrawable());
+        addTypeMapping(account("user2", "com.example"), "title", comExampleIcon);
+
+        final AccountDisplayInfoFactory sut = createFactoryForKnownTypes();
+
+        final AccountDisplayInfo displayable = sut.getAccountDisplayInfo(
+                account("user", "com.example"));
+        assertEquals(comExampleIcon, displayable.getIcon());
+    }
+
+    public void test_displayableAccount_hasNameFromAccount() {
+        final Drawable comExampleIcon = someDrawable();
+
+        addTypeMapping(account("user@example.com", "com.example"), "title", comExampleIcon);
+        addTypeMapping(account(null, null), "device", someDrawable());
+        addTypeMapping(account("foo", "bar.type"), "bar", someDrawable());
+        addTypeMapping(account("user2@example.com", "com.example"), "title", comExampleIcon);
+
+        final AccountDisplayInfoFactory sut = createFactoryForKnownTypes();
+
+        final AccountDisplayInfo displayable = sut.getAccountDisplayInfo(
+                account("user@example.com", "com.example"));
+        assertEquals("user@example.com", displayable.getNameLabel());
+    }
+
+    public void test_displayableAccountForNullAccount_hasNameFromAccountType() {
+        addSomeKnownAccounts();
+        addTypeMapping(account(null, null), "Device Display Label", someDrawable());
+
+        final AccountDisplayInfoFactory sut = createFactoryForKnownTypes();
+
+        final AccountDisplayInfo displayable = sut.getAccountDisplayInfo(
+                account(null, null));
+        assertEquals("Device Display Label", displayable.getNameLabel());
+    }
+
+    public void test_displayableAccountForDeviceAccount_hasNameFromAccountType() {
+        addSomeKnownAccounts();
+        addTypeMapping(account("some.device.account.name", "device.account.type"), "Device Label",
+                someDrawable());
+
+        final AccountDisplayInfoFactory sut = createFactoryForKnownTypes(
+                new FakeDeviceAccountTypeFactory().withDeviceTypes("device.account.type"));
+
+        final AccountDisplayInfo displayable = sut.getAccountDisplayInfo(
+                account("some.device.account.name", "device.account.type"));
+        assertEquals("Device Label", displayable.getNameLabel());
+    }
+
+    public void test_displayableAccountForDeviceAccountWhenMultiple_hasNameFromAccount() {
+        addSomeKnownAccounts();
+        addTypeMapping(account("first.device.account.name", "a.device.account.type"),
+                "Device Display Label", someDrawable());
+        addTypeMapping(account("second.device.account.name", "b.device.account.type"),
+                "Device Display Label", someDrawable());
+        addTypeMapping(account("another.device.account.name", "a.device.account.type"),
+                "Device Display Label", someDrawable());
+
+        final AccountDisplayInfoFactory sut = createFactoryForKnownTypes(
+                new FakeDeviceAccountTypeFactory().withDeviceTypes("a.device.account.type",
+                        "b.device.account.type"));
+
+        final AccountDisplayInfo displayable = sut.getAccountDisplayInfo(
+                account("first.device.account.name", "a.device.account.type"));
+        assertEquals("first.device.account.name", displayable.getNameLabel());
+
+        final AccountDisplayInfo displayable2 = sut.getAccountDisplayInfo(
+                account("second.device.account.name", "b.device.account.type"));
+        assertEquals("second.device.account.name", displayable2.getNameLabel());
+    }
+
+    public void test_displayableAccountForSimAccount_hasNameFromAccountType() {
+        addSomeKnownAccounts();
+        addTypeMapping(account("sim.account.name", "sim.account.type"), "SIM", someDrawable());
+
+        final AccountDisplayInfoFactory sut = createFactoryForKnownTypes(
+                new FakeDeviceAccountTypeFactory().withSimTypes("sim.account.type"));
+
+        final AccountDisplayInfo displayable = sut.getAccountDisplayInfo(
+                account("sim.account.name", "sim.account.type"));
+        assertEquals("SIM", displayable.getNameLabel());
+    }
+
+    public void test_displayableAccountForSimAccountWhenMultiple_hasNameFromAccount() {
+        addSomeKnownAccounts();
+        addTypeMapping(account("sim.account.name", "sim.account.type"), "SIM", someDrawable());
+        addTypeMapping(account("sim2.account.name", "sim.account.type"), "SIM", someDrawable());
+
+        final AccountDisplayInfoFactory sut = createFactoryForKnownTypes(
+                new FakeDeviceAccountTypeFactory().withSimTypes("sim.account.type"));
+
+        final AccountDisplayInfo displayable = sut.getAccountDisplayInfo(
+                account("sim.account.name", "sim.account.type"));
+        assertEquals("sim.account.name", displayable.getNameLabel());
+    }
+
+    private void addSomeKnownAccounts() {
+        final Drawable comExampleIcon = someDrawable();
+        addTypeMapping(account("user@example.com", "com.example"), "Example Title", comExampleIcon);
+        addTypeMapping(account("foo", "bar.type"), "Bar", someDrawable());
+        addTypeMapping(account("user2@example.com", "com.example"), "Example Title", comExampleIcon);
+        addTypeMapping(account("user", "com.example.two"), "Some Account", someDrawable());
+    }
+
+    private AccountDisplayInfoFactory createFactoryForKnownTypes() {
+        return createFactoryForKnownTypes(new DeviceLocalAccountTypeFactory.Default(getContext()));
+    }
+
+    private AccountDisplayInfoFactory createFactoryForKnownTypes(DeviceLocalAccountTypeFactory
+            typeFactory) {
+        return new AccountDisplayInfoFactory(getContext(),
+                createFakeAccountTypeManager(mKnownTypes), typeFactory,
+                new ArrayList<>(mKnownTypes.keySet()));
+    }
+
+    private AccountWithDataSet account(String name, String type) {
+        return new AccountWithDataSet(name, type, /* dataSet */ null);
+    }
+
+    private void addTypeMapping(AccountWithDataSet account, String label, Drawable icon) {
+        mKnownTypes.put(account, FakeAccountType.create(account, label, icon));
+    }
+
+    private AccountTypeManager createFakeAccountTypeManager(
+            final Map<AccountWithDataSet, AccountType> mapping) {
+        return new MockAccountTypeManager(mapping.values().toArray(new AccountType[mapping.size()]),
+                mapping.keySet().toArray(new AccountWithDataSet[mapping.size()]));
+    }
+
+    private Drawable someDrawable() {
+        return new Drawable() {
+            @Override
+            public void draw(Canvas canvas) {
+            }
+
+            @Override
+            public void setAlpha(int i) {
+            }
+
+            @Override
+            public void setColorFilter(ColorFilter colorFilter) {
+            }
+
+            @Override
+            public int getOpacity() {
+                return PixelFormat.OPAQUE;
+            }
+        };
+    }
+
+}