summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--proguard.flags10
-rw-r--r--src/com/android/contacts/common/util/DeviceLocalContactsFilterProvider.java34
-rw-r--r--tests/src/com/android/contacts/common/util/DeviceLocalContactsFilterProviderTests.java221
3 files changed, 252 insertions, 13 deletions
diff --git a/proguard.flags b/proguard.flags
index f1e609b9a..d9dad57b2 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -19,6 +19,7 @@
-keep class com.android.contacts.common.database.NoNullCursorAsyncQueryHandler { *; }
-keep class com.android.contacts.common.format.FormatUtils { *; }
-keep class com.android.contacts.common.format.TextHighlighter { *; }
+-keep class com.android.contacts.common.list.ContactListFilter { *; }
-keep class com.android.contacts.common.list.ContactListItemView { *; }
-keep class com.android.contacts.common.list.ContactsSectionIndexer { *; }
-keep class com.android.contacts.common.location.CountryDetector { *; }
@@ -66,6 +67,11 @@
-keep class com.android.contacts.common.util.NameConverter { *; }
-keep class com.android.contacts.common.util.SearchUtil { *; }
-keep class com.android.contacts.common.util.SearchUtil$* { *; }
+-keep class com.android.contacts.common.util.DeviceAccountFilter { *; }
+-keep class com.android.contacts.common.util.DeviceAccountFilter$* { *; }
+-keep class com.android.contacts.common.util.DeviceAccountPresentationValues { *; }
+-keep class com.android.contacts.common.util.DeviceAccountPresentationValues$* { *; }
+-keep class com.android.contacts.common.util.DeviceLocalContactsFilterProvider { *; }
-keep class com.android.contacts.ContactsApplication { *; }
-keep class com.android.contacts.ContactSaveService { *; }
-keep class com.android.contacts.ContactSaveService$* { *; }
@@ -86,11 +92,9 @@
-keep class com.google.common.collect.Multimap { *; }
-keep class com.google.common.collect.Sets { *; }
-# Any class or method annotated with NeededForTesting or NeededForReflection.
--keep @com.android.contacts.common.testing.NeededForTesting class *
+# Any class or method annotated with NeededForReflection.
-keep @com.android.contacts.test.NeededForReflection class *
-keepclassmembers class * {
-@com.android.contacts.common.testing.NeededForTesting *;
@com.android.contacts.test.NeededForReflection *;
}
diff --git a/src/com/android/contacts/common/util/DeviceLocalContactsFilterProvider.java b/src/com/android/contacts/common/util/DeviceLocalContactsFilterProvider.java
index 98b865d7e..1d06a4317 100644
--- a/src/com/android/contacts/common/util/DeviceLocalContactsFilterProvider.java
+++ b/src/com/android/contacts/common/util/DeviceLocalContactsFilterProvider.java
@@ -26,8 +26,11 @@ 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;
@@ -74,17 +77,9 @@ public class DeviceLocalContactsFilterProvider
@Override
public CursorLoader onCreateLoader(int i, Bundle bundle) {
- 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);
- }
+ if (mKnownAccountTypes == null) {
+ initKnownAccountTypes();
}
- mKnownAccountTypes = knownTypes.toArray(new String[knownTypes.size()]);
-
return new CursorLoader(mContext, getUri(), PROJECTION, getSelection(),
getSelectionArgs(), null);
}
@@ -128,6 +123,25 @@ public class DeviceLocalContactsFilterProvider
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) {
diff --git a/tests/src/com/android/contacts/common/util/DeviceLocalContactsFilterProviderTests.java b/tests/src/com/android/contacts/common/util/DeviceLocalContactsFilterProviderTests.java
new file mode 100644
index 000000000..d776ab8ee
--- /dev/null
+++ b/tests/src/com/android/contacts/common/util/DeviceLocalContactsFilterProviderTests.java
@@ -0,0 +1,221 @@
+/*
+ * 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;
+ }
+ }
+}