Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6086076d authored by Marcus Hagerott's avatar Marcus Hagerott
Browse files

resolve merge conflicts of bc510849 to master

Change-Id: I209fc7c8ade6a96ed2a2a6641f92b7a2258d315c
parents f57e6404 bc510849
Loading
Loading
Loading
Loading
+7 −3
Original line number Diff line number Diff line
@@ -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.
@@ -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));
+64 −29
Original line number Diff line number Diff line
@@ -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;

@@ -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,
@@ -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);
@@ -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()]);
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -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);
+14 −6
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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
@@ -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));
@@ -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));
+95 −9
Original line number Diff line number Diff line
@@ -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;
@@ -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 {
@@ -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",
@@ -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(
@@ -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;
    }

@@ -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
@@ -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);
        }
    }
}