Loading src/com/android/contacts/common/list/ContactListFilter.java +7 −3 Original line number Diff line number Diff line Loading @@ -98,6 +98,12 @@ public final class ContactListFilter implements Comparable<ContactListFilter>, P /* accountType= */ null, /* accountName= */ null, /* dataSet= */ null, icon); } public static ContactListFilter createDeviceContactsFilter(Drawable icon, AccountWithDataSet account) { return new ContactListFilter(ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS, account.type, account.name, account.dataSet, icon); } /** * Whether the given {@link ContactListFilter} has a filter type that should be displayed as * the default contacts list view. Loading Loading @@ -338,10 +344,8 @@ public final class ContactListFilter implements Comparable<ContactListFilter>, P } public AccountWithDataSet toAccountWithDataSet() { if (filterType == FILTER_TYPE_ACCOUNT) { if (filterType == FILTER_TYPE_ACCOUNT || filterType == FILTER_TYPE_DEVICE_CONTACTS) { 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)); Loading src/com/android/contacts/common/model/DeviceLocalAccountLocator.java +64 −29 Original line number Diff line number Diff line Loading @@ -18,10 +18,10 @@ package com.android.contacts.common.model; import android.accounts.AccountManager; import android.content.ContentResolver; import android.database.Cursor; import android.net.Uri; import android.provider.ContactsContract; import android.support.annotation.VisibleForTesting; import com.android.contacts.common.model.account.AccountType; import com.android.contacts.common.model.account.AccountWithDataSet; import com.android.contacts.common.util.DeviceLocalAccountTypeFactory; Loading @@ -39,6 +39,9 @@ import java.util.Set; */ public class DeviceLocalAccountLocator { // Note this class is assuming ACCOUNT_NAME and ACCOUNT_TYPE have same values in // RawContacts, Groups, and Settings. This assumption simplifies the code somewhat and it // is true right now and unlikely to ever change. @VisibleForTesting static String[] PROJECTION = new String[] { ContactsContract.RawContacts.ACCOUNT_NAME, ContactsContract.RawContacts.ACCOUNT_TYPE, Loading @@ -49,30 +52,59 @@ public class DeviceLocalAccountLocator { 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; private final String mSelection; private final String[] mSelectionArgs; public DeviceLocalAccountLocator(ContentResolver contentResolver, DeviceLocalAccountTypeFactory factory, List<AccountWithDataSet> knownAccounts) { mResolver = contentResolver; mAccountTypeFactory = factory; mKnownAccountTypes = new HashSet<>(); final Set<String> knownAccountTypes = new HashSet<>(); for (AccountWithDataSet account : knownAccounts) { mKnownAccountTypes.add(account.type); knownAccountTypes.add(account.type); } mSelection = getSelection(knownAccountTypes); mSelectionArgs = getSelectionArgs(knownAccountTypes); } 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<>(); // Many device accounts have default groups associated with them. addAccountsFromQuery(ContactsContract.Groups.CONTENT_URI, localAccounts); addAccountsFromQuery(ContactsContract.Settings.CONTENT_URI, localAccounts); if (localAccounts.isEmpty()) { // It's probably safe to assume that if one of the earlier queries found a "device" // account then this query isn't going to find any different device accounts. // We skip this query because it probably is kind of expensive (relative to the other // queries). addAccountsFromQuery(ContactsContract.RawContacts.CONTENT_URI, localAccounts); } return new ArrayList<>(localAccounts); } private void addAccountsFromQuery(Uri uri, Set<AccountWithDataSet> accounts) { final Cursor cursor = mResolver.query(uri, PROJECTION, mSelection, mSelectionArgs, null); if (cursor == null) return; try { addAccountsFromCursor(cursor, accounts); } finally { cursor.close(); } } private void addAccountsFromCursor(Cursor cursor, Set<AccountWithDataSet> accounts) { while (cursor.moveToNext()) { final String name = cursor.getString(COL_NAME); final String type = cursor.getString(COL_TYPE); Loading @@ -80,36 +112,39 @@ public class DeviceLocalAccountLocator { if (DeviceLocalAccountTypeFactory.Util.isLocalAccountType( mAccountTypeFactory, type)) { localAccounts.add(new AccountWithDataSet(name, type, dataSet)); accounts.add(new AccountWithDataSet(name, type, dataSet)); } } } finally { cursor.close(); } return new ArrayList<>(localAccounts); @VisibleForTesting public String getSelection() { return mSelection; } @VisibleForTesting public String getSelection() { final StringBuilder sb = new StringBuilder(); sb.append(ContactsContract.RawContacts.DELETED).append(" =0 AND (") public String[] getSelectionArgs() { return mSelectionArgs; } private static String getSelection(Set<String> knownAccountTypes) { final StringBuilder sb = new StringBuilder() .append(ContactsContract.RawContacts.ACCOUNT_TYPE).append(" IS NULL"); if (mKnownAccountTypes.isEmpty()) { return sb.append(')').toString(); if (knownAccountTypes.isEmpty()) { return sb.toString(); } sb.append(" OR ").append(ContactsContract.RawContacts.ACCOUNT_TYPE).append(" NOT IN ("); for (String ignored : mKnownAccountTypes) { for (String ignored : knownAccountTypes) { sb.append("?,"); } // Remove trailing ',' sb.deleteCharAt(sb.length() - 1).append(')').append(')'); sb.deleteCharAt(sb.length() - 1).append(')'); return sb.toString(); } @VisibleForTesting public String[] getSelectionArgs() { return mKnownAccountTypes.toArray(new String[mKnownAccountTypes.size()]); private static String[] getSelectionArgs(Set<String> knownAccountTypes) { if (knownAccountTypes.isEmpty()) return null; return knownAccountTypes.toArray(new String[knownAccountTypes.size()]); } } src/com/android/contacts/common/model/account/AccountWithDataSet.java +1 −0 Original line number Diff line number Diff line Loading @@ -139,6 +139,7 @@ public class AccountWithDataSet implements Parcelable { args = new String[] {type, name, dataSet}; } } selection += " AND " + RawContacts.DELETED + "=0"; final Cursor c = context.getContentResolver().query(RAW_CONTACTS_URI_LIMIT_1, ID_PROJECTION, selection, args, null); Loading src/com/android/contacts/common/util/AccountFilterUtil.java +14 −6 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import android.content.AsyncTaskLoader; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.database.Cursor; import android.graphics.drawable.Drawable; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Intents; Loading @@ -37,8 +38,10 @@ import com.android.contacts.common.list.AccountFilterActivity; 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.RawContact; import com.android.contacts.common.model.account.AccountType; import com.android.contacts.common.model.account.AccountWithDataSet; import com.android.contactsbind.ObjectFactory; import com.google.common.collect.Lists; import java.util.ArrayList; Loading Loading @@ -96,15 +99,17 @@ public class AccountFilterUtil { */ public static class FilterLoader extends AsyncTaskLoader<List<ContactListFilter>> { private Context mContext; private DeviceLocalAccountTypeFactory mDeviceLocalFactory; public FilterLoader(Context context) { super(context); mContext = context; mDeviceLocalFactory = ObjectFactory.getDeviceLocalAccountTypeFactory(context); } @Override public List<ContactListFilter> loadInBackground() { return loadAccountFilters(mContext); return loadAccountFilters(mContext, mDeviceLocalFactory); } @Override Loading @@ -123,7 +128,8 @@ public class AccountFilterUtil { } } private static List<ContactListFilter> loadAccountFilters(Context context) { private static List<ContactListFilter> loadAccountFilters(Context context, DeviceLocalAccountTypeFactory deviceAccountTypeFactory) { final ArrayList<ContactListFilter> accountFilters = Lists.newArrayList(); final AccountTypeManager accountTypeManager = AccountTypeManager.getInstance(context); accountTypeManager.sortAccounts(/* defaultAccount */ getDefaultAccount(context)); Loading @@ -133,13 +139,15 @@ public class AccountFilterUtil { for (AccountWithDataSet account : accounts) { final AccountType accountType = accountTypeManager.getAccountType(account.type, account.dataSet); if (accountType.isExtension() && !account.hasData(context)) { // Hide extensions with no raw_contacts. if ((accountType.isExtension() || DeviceLocalAccountTypeFactory.Util.isLocalAccountType( deviceAccountTypeFactory, account.type)) && !account.hasData(context)) { // Hide extensions and device accounts with no raw_contacts. continue; } final Drawable icon = accountType != null ? accountType.getDisplayIcon(context) : null; if (account.isLocalAccount()) { accountFilters.add(ContactListFilter.createDeviceContactsFilter(icon)); if (DeviceLocalAccountTypeFactory.Util.isLocalAccountType( deviceAccountTypeFactory, account.type)) { accountFilters.add(ContactListFilter.createDeviceContactsFilter(icon, account)); } else { accountFilters.add(ContactListFilter.createAccountFilter( account.type, account.name, account.dataSet, icon)); Loading tests/src/com/android/contacts/common/model/DeviceLocalAccountLocatorTests.java +95 −9 Original line number Diff line number Diff line Loading @@ -15,6 +15,7 @@ */ package com.android.contacts.common.model; import android.content.ContentProvider; import android.content.ContentResolver; import android.database.Cursor; import android.database.MatrixCursor; Loading @@ -27,17 +28,16 @@ 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.HashMap; import java.util.List; import java.util.Map; @SmallTest public class DeviceLocalAccountLocatorTests extends AndroidTestCase { Loading @@ -53,6 +53,11 @@ public class DeviceLocalAccountLocatorTests extends AndroidTestCase { // We didn't throw so it passed } public void test_getDeviceLocalAccounts_returnsEmptyListWhenQueryReturnsNull() { final DeviceLocalAccountLocator sut = createWithQueryResult(null); assertTrue(sut.getDeviceLocalAccounts().isEmpty()); } public void test_getDeviceLocalAccounts_returnsEmptyListWhenNoRawContactsHaveDeviceType() { final DeviceLocalAccountLocator sut = createWithQueryResult(queryResult( "user", "com.example", Loading Loading @@ -107,6 +112,52 @@ public class DeviceLocalAccountLocatorTests extends AndroidTestCase { assertTrue("Selection args is missing an expected value", args.contains("com.example.1")); } public void test_getDeviceLocalAccounts_includesAccountsFromSettings() { final DeviceLocalAccountTypeFactory stubFactory = new FakeDeviceAccountTypeFactory() .withDeviceTypes(null, "vnd.sec.contact.phone") .withSimTypes("vnd.sec.contact.sim"); final DeviceLocalAccountLocator sut = new DeviceLocalAccountLocator( createContentResolverWithProvider(new FakeContactsProvider() .withQueryResult(ContactsContract.Settings.CONTENT_URI, queryResult( "phone_account", "vnd.sec.contact.phone", "sim_account", "vnd.sec.contact.sim" ))), stubFactory, Collections.<AccountWithDataSet>emptyList()); assertEquals(2, sut.getDeviceLocalAccounts().size()); } public void test_getDeviceLocalAccounts_includesAccountsFromGroups() { final DeviceLocalAccountTypeFactory stubFactory = new FakeDeviceAccountTypeFactory() .withDeviceTypes(null, "vnd.sec.contact.phone") .withSimTypes("vnd.sec.contact.sim"); final DeviceLocalAccountLocator sut = new DeviceLocalAccountLocator( createContentResolverWithProvider(new FakeContactsProvider() .withQueryResult(ContactsContract.Groups.CONTENT_URI, queryResult( "phone_account", "vnd.sec.contact.phone", "sim_account", "vnd.sec.contact.sim" ))), stubFactory, Collections.<AccountWithDataSet>emptyList()); assertEquals(2, sut.getDeviceLocalAccounts().size()); } public void test_getDeviceLocalAccounts_onlyQueriesRawContactsIfNecessary() { final DeviceLocalAccountTypeFactory stubFactory = new FakeDeviceAccountTypeFactory() .withDeviceTypes(null, "vnd.sec.contact.phone") .withSimTypes("vnd.sec.contact.sim"); final FakeContactsProvider contactsProvider = new FakeContactsProvider() .withQueryResult(ContactsContract.Groups.CONTENT_URI, queryResult( "phone_account", "vnd.sec.contact.phone", "sim_account", "vnd.sec.contact.sim" )); final DeviceLocalAccountLocator sut = new DeviceLocalAccountLocator( createContentResolverWithProvider(contactsProvider), stubFactory, Collections.<AccountWithDataSet>emptyList()); sut.getDeviceLocalAccounts(); assertEquals(0, contactsProvider.getQueryCountFor(RawContacts.CONTENT_URI)); } private DeviceLocalAccountLocator createWithQueryResult( Cursor cursor) { final DeviceLocalAccountLocator locator = new DeviceLocalAccountLocator( Loading @@ -116,10 +167,17 @@ public class DeviceLocalAccountLocatorTests extends AndroidTestCase { return locator; } private ContentResolver createContentResolverWithProvider(ContentProvider contactsProvider) { final MockContentResolver resolver = new MockContentResolver(); resolver.addProvider(ContactsContract.AUTHORITY, contactsProvider); return resolver; } private ContentResolver createStubResolverWithContentQueryResult(Cursor cursor) { final MockContentResolver resolver = new MockContentResolver(); resolver.addProvider(ContactsContract.AUTHORITY, new FakeContactsProvider(cursor)); resolver.addProvider(ContactsContract.AUTHORITY, new FakeContactsProvider() .withDefaultQueryResult(cursor)); return resolver; } Loading @@ -135,15 +193,19 @@ public class DeviceLocalAccountLocatorTests extends AndroidTestCase { private static class FakeContactsProvider extends MockContentProvider { public Cursor mNextQueryResult; public Map<Uri, Cursor> mNextResultMapping = new HashMap<>(); public Map<Uri, Integer> mQueryCountMapping = new HashMap<>(); public FakeContactsProvider() {} public FakeContactsProvider(Cursor result) { mNextQueryResult = result; public FakeContactsProvider withDefaultQueryResult(Cursor cursor) { mNextQueryResult = cursor; return this; } public void setNextQueryResult(Cursor result) { mNextQueryResult = result; public FakeContactsProvider withQueryResult(Uri uri, Cursor cursor) { mNextResultMapping.put(uri, cursor); return this; } @Override Loading @@ -152,11 +214,35 @@ public class DeviceLocalAccountLocatorTests extends AndroidTestCase { return query(uri, projection, selection, selectionArgs, sortOrder, null); } public int getQueryCountFor(Uri uri) { ensureCountInitialized(uri); return mQueryCountMapping.get(uri); } @Nullable @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) { incrementQueryCount(uri); final Cursor result = mNextResultMapping.get(uri); if (result == null) { return mNextQueryResult; } else { return result; } } private void ensureCountInitialized(Uri uri) { if (!mQueryCountMapping.containsKey(uri)) { mQueryCountMapping.put(uri, 0); } } private void incrementQueryCount(Uri uri) { ensureCountInitialized(uri); final int count = mQueryCountMapping.get(uri); mQueryCountMapping.put(uri, count + 1); } } } Loading
src/com/android/contacts/common/list/ContactListFilter.java +7 −3 Original line number Diff line number Diff line Loading @@ -98,6 +98,12 @@ public final class ContactListFilter implements Comparable<ContactListFilter>, P /* accountType= */ null, /* accountName= */ null, /* dataSet= */ null, icon); } public static ContactListFilter createDeviceContactsFilter(Drawable icon, AccountWithDataSet account) { return new ContactListFilter(ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS, account.type, account.name, account.dataSet, icon); } /** * Whether the given {@link ContactListFilter} has a filter type that should be displayed as * the default contacts list view. Loading Loading @@ -338,10 +344,8 @@ public final class ContactListFilter implements Comparable<ContactListFilter>, P } public AccountWithDataSet toAccountWithDataSet() { if (filterType == FILTER_TYPE_ACCOUNT) { if (filterType == FILTER_TYPE_ACCOUNT || filterType == FILTER_TYPE_DEVICE_CONTACTS) { 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)); Loading
src/com/android/contacts/common/model/DeviceLocalAccountLocator.java +64 −29 Original line number Diff line number Diff line Loading @@ -18,10 +18,10 @@ package com.android.contacts.common.model; import android.accounts.AccountManager; import android.content.ContentResolver; import android.database.Cursor; import android.net.Uri; import android.provider.ContactsContract; import android.support.annotation.VisibleForTesting; import com.android.contacts.common.model.account.AccountType; import com.android.contacts.common.model.account.AccountWithDataSet; import com.android.contacts.common.util.DeviceLocalAccountTypeFactory; Loading @@ -39,6 +39,9 @@ import java.util.Set; */ public class DeviceLocalAccountLocator { // Note this class is assuming ACCOUNT_NAME and ACCOUNT_TYPE have same values in // RawContacts, Groups, and Settings. This assumption simplifies the code somewhat and it // is true right now and unlikely to ever change. @VisibleForTesting static String[] PROJECTION = new String[] { ContactsContract.RawContacts.ACCOUNT_NAME, ContactsContract.RawContacts.ACCOUNT_TYPE, Loading @@ -49,30 +52,59 @@ public class DeviceLocalAccountLocator { 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; private final String mSelection; private final String[] mSelectionArgs; public DeviceLocalAccountLocator(ContentResolver contentResolver, DeviceLocalAccountTypeFactory factory, List<AccountWithDataSet> knownAccounts) { mResolver = contentResolver; mAccountTypeFactory = factory; mKnownAccountTypes = new HashSet<>(); final Set<String> knownAccountTypes = new HashSet<>(); for (AccountWithDataSet account : knownAccounts) { mKnownAccountTypes.add(account.type); knownAccountTypes.add(account.type); } mSelection = getSelection(knownAccountTypes); mSelectionArgs = getSelectionArgs(knownAccountTypes); } 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<>(); // Many device accounts have default groups associated with them. addAccountsFromQuery(ContactsContract.Groups.CONTENT_URI, localAccounts); addAccountsFromQuery(ContactsContract.Settings.CONTENT_URI, localAccounts); if (localAccounts.isEmpty()) { // It's probably safe to assume that if one of the earlier queries found a "device" // account then this query isn't going to find any different device accounts. // We skip this query because it probably is kind of expensive (relative to the other // queries). addAccountsFromQuery(ContactsContract.RawContacts.CONTENT_URI, localAccounts); } return new ArrayList<>(localAccounts); } private void addAccountsFromQuery(Uri uri, Set<AccountWithDataSet> accounts) { final Cursor cursor = mResolver.query(uri, PROJECTION, mSelection, mSelectionArgs, null); if (cursor == null) return; try { addAccountsFromCursor(cursor, accounts); } finally { cursor.close(); } } private void addAccountsFromCursor(Cursor cursor, Set<AccountWithDataSet> accounts) { while (cursor.moveToNext()) { final String name = cursor.getString(COL_NAME); final String type = cursor.getString(COL_TYPE); Loading @@ -80,36 +112,39 @@ public class DeviceLocalAccountLocator { if (DeviceLocalAccountTypeFactory.Util.isLocalAccountType( mAccountTypeFactory, type)) { localAccounts.add(new AccountWithDataSet(name, type, dataSet)); accounts.add(new AccountWithDataSet(name, type, dataSet)); } } } finally { cursor.close(); } return new ArrayList<>(localAccounts); @VisibleForTesting public String getSelection() { return mSelection; } @VisibleForTesting public String getSelection() { final StringBuilder sb = new StringBuilder(); sb.append(ContactsContract.RawContacts.DELETED).append(" =0 AND (") public String[] getSelectionArgs() { return mSelectionArgs; } private static String getSelection(Set<String> knownAccountTypes) { final StringBuilder sb = new StringBuilder() .append(ContactsContract.RawContacts.ACCOUNT_TYPE).append(" IS NULL"); if (mKnownAccountTypes.isEmpty()) { return sb.append(')').toString(); if (knownAccountTypes.isEmpty()) { return sb.toString(); } sb.append(" OR ").append(ContactsContract.RawContacts.ACCOUNT_TYPE).append(" NOT IN ("); for (String ignored : mKnownAccountTypes) { for (String ignored : knownAccountTypes) { sb.append("?,"); } // Remove trailing ',' sb.deleteCharAt(sb.length() - 1).append(')').append(')'); sb.deleteCharAt(sb.length() - 1).append(')'); return sb.toString(); } @VisibleForTesting public String[] getSelectionArgs() { return mKnownAccountTypes.toArray(new String[mKnownAccountTypes.size()]); private static String[] getSelectionArgs(Set<String> knownAccountTypes) { if (knownAccountTypes.isEmpty()) return null; return knownAccountTypes.toArray(new String[knownAccountTypes.size()]); } }
src/com/android/contacts/common/model/account/AccountWithDataSet.java +1 −0 Original line number Diff line number Diff line Loading @@ -139,6 +139,7 @@ public class AccountWithDataSet implements Parcelable { args = new String[] {type, name, dataSet}; } } selection += " AND " + RawContacts.DELETED + "=0"; final Cursor c = context.getContentResolver().query(RAW_CONTACTS_URI_LIMIT_1, ID_PROJECTION, selection, args, null); Loading
src/com/android/contacts/common/util/AccountFilterUtil.java +14 −6 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import android.content.AsyncTaskLoader; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.database.Cursor; import android.graphics.drawable.Drawable; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Intents; Loading @@ -37,8 +38,10 @@ import com.android.contacts.common.list.AccountFilterActivity; 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.RawContact; import com.android.contacts.common.model.account.AccountType; import com.android.contacts.common.model.account.AccountWithDataSet; import com.android.contactsbind.ObjectFactory; import com.google.common.collect.Lists; import java.util.ArrayList; Loading Loading @@ -96,15 +99,17 @@ public class AccountFilterUtil { */ public static class FilterLoader extends AsyncTaskLoader<List<ContactListFilter>> { private Context mContext; private DeviceLocalAccountTypeFactory mDeviceLocalFactory; public FilterLoader(Context context) { super(context); mContext = context; mDeviceLocalFactory = ObjectFactory.getDeviceLocalAccountTypeFactory(context); } @Override public List<ContactListFilter> loadInBackground() { return loadAccountFilters(mContext); return loadAccountFilters(mContext, mDeviceLocalFactory); } @Override Loading @@ -123,7 +128,8 @@ public class AccountFilterUtil { } } private static List<ContactListFilter> loadAccountFilters(Context context) { private static List<ContactListFilter> loadAccountFilters(Context context, DeviceLocalAccountTypeFactory deviceAccountTypeFactory) { final ArrayList<ContactListFilter> accountFilters = Lists.newArrayList(); final AccountTypeManager accountTypeManager = AccountTypeManager.getInstance(context); accountTypeManager.sortAccounts(/* defaultAccount */ getDefaultAccount(context)); Loading @@ -133,13 +139,15 @@ public class AccountFilterUtil { for (AccountWithDataSet account : accounts) { final AccountType accountType = accountTypeManager.getAccountType(account.type, account.dataSet); if (accountType.isExtension() && !account.hasData(context)) { // Hide extensions with no raw_contacts. if ((accountType.isExtension() || DeviceLocalAccountTypeFactory.Util.isLocalAccountType( deviceAccountTypeFactory, account.type)) && !account.hasData(context)) { // Hide extensions and device accounts with no raw_contacts. continue; } final Drawable icon = accountType != null ? accountType.getDisplayIcon(context) : null; if (account.isLocalAccount()) { accountFilters.add(ContactListFilter.createDeviceContactsFilter(icon)); if (DeviceLocalAccountTypeFactory.Util.isLocalAccountType( deviceAccountTypeFactory, account.type)) { accountFilters.add(ContactListFilter.createDeviceContactsFilter(icon, account)); } else { accountFilters.add(ContactListFilter.createAccountFilter( account.type, account.name, account.dataSet, icon)); Loading
tests/src/com/android/contacts/common/model/DeviceLocalAccountLocatorTests.java +95 −9 Original line number Diff line number Diff line Loading @@ -15,6 +15,7 @@ */ package com.android.contacts.common.model; import android.content.ContentProvider; import android.content.ContentResolver; import android.database.Cursor; import android.database.MatrixCursor; Loading @@ -27,17 +28,16 @@ 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.HashMap; import java.util.List; import java.util.Map; @SmallTest public class DeviceLocalAccountLocatorTests extends AndroidTestCase { Loading @@ -53,6 +53,11 @@ public class DeviceLocalAccountLocatorTests extends AndroidTestCase { // We didn't throw so it passed } public void test_getDeviceLocalAccounts_returnsEmptyListWhenQueryReturnsNull() { final DeviceLocalAccountLocator sut = createWithQueryResult(null); assertTrue(sut.getDeviceLocalAccounts().isEmpty()); } public void test_getDeviceLocalAccounts_returnsEmptyListWhenNoRawContactsHaveDeviceType() { final DeviceLocalAccountLocator sut = createWithQueryResult(queryResult( "user", "com.example", Loading Loading @@ -107,6 +112,52 @@ public class DeviceLocalAccountLocatorTests extends AndroidTestCase { assertTrue("Selection args is missing an expected value", args.contains("com.example.1")); } public void test_getDeviceLocalAccounts_includesAccountsFromSettings() { final DeviceLocalAccountTypeFactory stubFactory = new FakeDeviceAccountTypeFactory() .withDeviceTypes(null, "vnd.sec.contact.phone") .withSimTypes("vnd.sec.contact.sim"); final DeviceLocalAccountLocator sut = new DeviceLocalAccountLocator( createContentResolverWithProvider(new FakeContactsProvider() .withQueryResult(ContactsContract.Settings.CONTENT_URI, queryResult( "phone_account", "vnd.sec.contact.phone", "sim_account", "vnd.sec.contact.sim" ))), stubFactory, Collections.<AccountWithDataSet>emptyList()); assertEquals(2, sut.getDeviceLocalAccounts().size()); } public void test_getDeviceLocalAccounts_includesAccountsFromGroups() { final DeviceLocalAccountTypeFactory stubFactory = new FakeDeviceAccountTypeFactory() .withDeviceTypes(null, "vnd.sec.contact.phone") .withSimTypes("vnd.sec.contact.sim"); final DeviceLocalAccountLocator sut = new DeviceLocalAccountLocator( createContentResolverWithProvider(new FakeContactsProvider() .withQueryResult(ContactsContract.Groups.CONTENT_URI, queryResult( "phone_account", "vnd.sec.contact.phone", "sim_account", "vnd.sec.contact.sim" ))), stubFactory, Collections.<AccountWithDataSet>emptyList()); assertEquals(2, sut.getDeviceLocalAccounts().size()); } public void test_getDeviceLocalAccounts_onlyQueriesRawContactsIfNecessary() { final DeviceLocalAccountTypeFactory stubFactory = new FakeDeviceAccountTypeFactory() .withDeviceTypes(null, "vnd.sec.contact.phone") .withSimTypes("vnd.sec.contact.sim"); final FakeContactsProvider contactsProvider = new FakeContactsProvider() .withQueryResult(ContactsContract.Groups.CONTENT_URI, queryResult( "phone_account", "vnd.sec.contact.phone", "sim_account", "vnd.sec.contact.sim" )); final DeviceLocalAccountLocator sut = new DeviceLocalAccountLocator( createContentResolverWithProvider(contactsProvider), stubFactory, Collections.<AccountWithDataSet>emptyList()); sut.getDeviceLocalAccounts(); assertEquals(0, contactsProvider.getQueryCountFor(RawContacts.CONTENT_URI)); } private DeviceLocalAccountLocator createWithQueryResult( Cursor cursor) { final DeviceLocalAccountLocator locator = new DeviceLocalAccountLocator( Loading @@ -116,10 +167,17 @@ public class DeviceLocalAccountLocatorTests extends AndroidTestCase { return locator; } private ContentResolver createContentResolverWithProvider(ContentProvider contactsProvider) { final MockContentResolver resolver = new MockContentResolver(); resolver.addProvider(ContactsContract.AUTHORITY, contactsProvider); return resolver; } private ContentResolver createStubResolverWithContentQueryResult(Cursor cursor) { final MockContentResolver resolver = new MockContentResolver(); resolver.addProvider(ContactsContract.AUTHORITY, new FakeContactsProvider(cursor)); resolver.addProvider(ContactsContract.AUTHORITY, new FakeContactsProvider() .withDefaultQueryResult(cursor)); return resolver; } Loading @@ -135,15 +193,19 @@ public class DeviceLocalAccountLocatorTests extends AndroidTestCase { private static class FakeContactsProvider extends MockContentProvider { public Cursor mNextQueryResult; public Map<Uri, Cursor> mNextResultMapping = new HashMap<>(); public Map<Uri, Integer> mQueryCountMapping = new HashMap<>(); public FakeContactsProvider() {} public FakeContactsProvider(Cursor result) { mNextQueryResult = result; public FakeContactsProvider withDefaultQueryResult(Cursor cursor) { mNextQueryResult = cursor; return this; } public void setNextQueryResult(Cursor result) { mNextQueryResult = result; public FakeContactsProvider withQueryResult(Uri uri, Cursor cursor) { mNextResultMapping.put(uri, cursor); return this; } @Override Loading @@ -152,11 +214,35 @@ public class DeviceLocalAccountLocatorTests extends AndroidTestCase { return query(uri, projection, selection, selectionArgs, sortOrder, null); } public int getQueryCountFor(Uri uri) { ensureCountInitialized(uri); return mQueryCountMapping.get(uri); } @Nullable @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) { incrementQueryCount(uri); final Cursor result = mNextResultMapping.get(uri); if (result == null) { return mNextQueryResult; } else { return result; } } private void ensureCountInitialized(Uri uri) { if (!mQueryCountMapping.containsKey(uri)) { mQueryCountMapping.put(uri, 0); } } private void incrementQueryCount(Uri uri) { ensureCountInitialized(uri); final int count = mQueryCountMapping.get(uri); mQueryCountMapping.put(uri, count + 1); } } }