Merge "Import translations. DO NOT MERGE" into ub-contactsdialer-g-dev
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index 7358fb7..d100d45 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -1490,7 +1490,8 @@
 
         if (getSupportActionBar() != null) {
             String actionBarTitle;
-            if (filter.filterType == ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS) {
+            if (filter.filterType == ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS &&
+                    filter.accountName == null) {
                 actionBarTitle = getString(R.string.account_phone);
             } else if (!TextUtils.isEmpty(filter.accountName)) {
                 actionBarTitle = getActionBarTitleForAccount(filter);
diff --git a/src/com/android/contacts/common/list/ContactListFilter.java b/src/com/android/contacts/common/list/ContactListFilter.java
index 4c8ca89..81ee5c9 100644
--- a/src/com/android/contacts/common/list/ContactListFilter.java
+++ b/src/com/android/contacts/common/list/ContactListFilter.java
@@ -93,6 +93,12 @@
                 /* 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.
@@ -333,10 +339,8 @@
     }
 
     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));
diff --git a/src/com/android/contacts/common/list/CustomContactListFilterActivity.java b/src/com/android/contacts/common/list/CustomContactListFilterActivity.java
index 4de57bb..b961b67 100644
--- a/src/com/android/contacts/common/list/CustomContactListFilterActivity.java
+++ b/src/com/android/contacts/common/list/CustomContactListFilterActivity.java
@@ -366,12 +366,13 @@
                 }
             } else {
                 final Integer titleRes = getAsInteger(Groups.TITLE_RES);
-                if (titleRes != null) {
+                if (titleRes != null && titleRes != 0) {
                     final String packageName = getAsString(Groups.RES_PACKAGE);
-                    return context.getPackageManager().getText(packageName, titleRes, null);
-                } else {
-                    return getAsString(Groups.TITLE);
+                    if (packageName != null) {
+                        return context.getPackageManager().getText(packageName, titleRes, null);
+                    }
                 }
+                return getAsString(Groups.TITLE);
             }
         }
 
diff --git a/src/com/android/contacts/common/model/DeviceLocalAccountLocator.java b/src/com/android/contacts/common/model/DeviceLocalAccountLocator.java
index 8997ed4..0efadc4 100644
--- a/src/com/android/contacts/common/model/DeviceLocalAccountLocator.java
+++ b/src/com/android/contacts/common/model/DeviceLocalAccountLocator.java
@@ -18,10 +18,10 @@
 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 @@
  */
 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,67 +52,99 @@
     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<>();
-        try {
-            while (cursor.moveToNext()) {
-                final String name = cursor.getString(COL_NAME);
-                final String type = cursor.getString(COL_TYPE);
-                final String dataSet = cursor.getString(COL_DATA_SET);
 
-                if (DeviceLocalAccountTypeFactory.Util.isLocalAccountType(
-                        mAccountTypeFactory, type)) {
-                    localAccounts.add(new AccountWithDataSet(name, type, dataSet));
-                }
-            }
-        } finally {
-            cursor.close();
+        // 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);
+            final String dataSet = cursor.getString(COL_DATA_SET);
+
+            if (DeviceLocalAccountTypeFactory.Util.isLocalAccountType(
+                    mAccountTypeFactory, type)) {
+                accounts.add(new AccountWithDataSet(name, type, dataSet));
+            }
+        }
+    }
+
     @VisibleForTesting
     public String getSelection() {
-        final StringBuilder sb = new StringBuilder();
-        sb.append(ContactsContract.RawContacts.DELETED).append(" =0 AND (")
-                .append(ContactsContract.RawContacts.ACCOUNT_TYPE).append(" IS NULL");
-        if (mKnownAccountTypes.isEmpty()) {
-            return sb.append(')').toString();
-        }
-        sb.append(" OR ").append(ContactsContract.RawContacts.ACCOUNT_TYPE).append(" NOT IN (");
-        for (String ignored : mKnownAccountTypes) {
-            sb.append("?,");
-        }
-        // Remove trailing ','
-        sb.deleteCharAt(sb.length() - 1).append(')').append(')');
-
-        return sb.toString();
+        return mSelection;
     }
 
     @VisibleForTesting
     public String[] getSelectionArgs() {
-        return mKnownAccountTypes.toArray(new String[mKnownAccountTypes.size()]);
+        return mSelectionArgs;
+    }
+
+    private static String getSelection(Set<String> knownAccountTypes) {
+        final StringBuilder sb = new StringBuilder()
+                .append(ContactsContract.RawContacts.ACCOUNT_TYPE).append(" IS NULL");
+        if (knownAccountTypes.isEmpty()) {
+            return sb.toString();
+        }
+        sb.append(" OR ").append(ContactsContract.RawContacts.ACCOUNT_TYPE).append(" NOT IN (");
+        for (String ignored : knownAccountTypes) {
+            sb.append("?,");
+        }
+        // Remove trailing ','
+        sb.deleteCharAt(sb.length() - 1).append(')');
+        return sb.toString();
+    }
+
+    private static String[] getSelectionArgs(Set<String> knownAccountTypes) {
+        if (knownAccountTypes.isEmpty()) return null;
+
+        return knownAccountTypes.toArray(new String[knownAccountTypes.size()]);
     }
 }
diff --git a/src/com/android/contacts/common/model/account/AccountWithDataSet.java b/src/com/android/contacts/common/model/account/AccountWithDataSet.java
index 887cdd4..ecd8f27 100644
--- a/src/com/android/contacts/common/model/account/AccountWithDataSet.java
+++ b/src/com/android/contacts/common/model/account/AccountWithDataSet.java
@@ -139,6 +139,7 @@
                 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);
diff --git a/src/com/android/contacts/common/util/AccountFilterUtil.java b/src/com/android/contacts/common/util/AccountFilterUtil.java
index 2d59981..b4db48d 100644
--- a/src/com/android/contacts/common/util/AccountFilterUtil.java
+++ b/src/com/android/contacts/common/util/AccountFilterUtil.java
@@ -22,7 +22,9 @@
 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;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -31,8 +33,10 @@
 import com.android.contacts.common.list.ContactListFilter;
 import com.android.contacts.common.list.ContactListFilterController;
 import com.android.contacts.common.model.AccountTypeManager;
+import com.android.contacts.common.model.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;
@@ -90,15 +94,17 @@
      */
     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
@@ -117,7 +123,8 @@
         }
     }
 
-    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));
@@ -127,13 +134,15 @@
         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));
diff --git a/src/com/android/contacts/editor/CompactRawContactsEditorView.java b/src/com/android/contacts/editor/CompactRawContactsEditorView.java
index f465167..5c3c565 100644
--- a/src/com/android/contacts/editor/CompactRawContactsEditorView.java
+++ b/src/com/android/contacts/editor/CompactRawContactsEditorView.java
@@ -16,22 +16,6 @@
 
 package com.android.contacts.editor;
 
-import com.android.contacts.common.model.account.AccountDisplayInfo;
-import com.android.contacts.common.model.account.AccountDisplayInfoFactory;
-import com.android.contacts.R;
-import com.android.contacts.common.model.AccountTypeManager;
-import com.android.contacts.common.model.RawContactDelta;
-import com.android.contacts.common.model.RawContactDeltaList;
-import com.android.contacts.common.model.RawContactModifier;
-import com.android.contacts.common.model.ValuesDelta;
-import com.android.contacts.common.model.account.AccountType;
-import com.android.contacts.common.model.account.AccountWithDataSet;
-import com.android.contacts.common.model.dataitem.DataKind;
-import com.android.contacts.common.util.AccountsListAdapter;
-import com.android.contacts.common.util.MaterialColorMapUtils;
-import com.android.contacts.util.UiClosables;
-
-import android.animation.LayoutTransition;
 import android.content.ContentUris;
 import android.content.Context;
 import android.database.Cursor;
@@ -69,6 +53,21 @@
 import android.widget.ListPopupWindow;
 import android.widget.TextView;
 
+import com.android.contacts.R;
+import com.android.contacts.common.model.AccountTypeManager;
+import com.android.contacts.common.model.RawContactDelta;
+import com.android.contacts.common.model.RawContactDeltaList;
+import com.android.contacts.common.model.RawContactModifier;
+import com.android.contacts.common.model.ValuesDelta;
+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.model.dataitem.DataKind;
+import com.android.contacts.common.util.AccountsListAdapter;
+import com.android.contacts.common.util.MaterialColorMapUtils;
+import com.android.contacts.util.UiClosables;
+
 import java.io.FileNotFoundException;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -617,11 +616,6 @@
             MaterialColorMapUtils.MaterialPalette materialPalette, ViewIdGenerator viewIdGenerator,
             long photoId, boolean hasNewContact, boolean isUserProfile,
             AccountWithDataSet primaryAccount) {
-        // Enable layout animations for new contacts. This looks nicer when switching to and from
-        // an account that doesn't support profile photos (e.g. SIM accounts).
-        if (hasNewContact && getLayoutTransition() == null) {
-            setLayoutTransition(new LayoutTransition());
-        }
         mKindSectionDataMap.clear();
         mKindSectionViewsMap.clear();
         mKindSectionViews.removeAllViews();
diff --git a/src/com/android/contacts/editor/GroupMembershipView.java b/src/com/android/contacts/editor/GroupMembershipView.java
index ba0c2c6..a76e408 100644
--- a/src/com/android/contacts/editor/GroupMembershipView.java
+++ b/src/com/android/contacts/editor/GroupMembershipView.java
@@ -181,6 +181,8 @@
         mPrimaryTextColor = resources.getColor(R.color.primary_text_color);
         mHintTextColor = resources.getColor(R.color.editor_disabled_text_color);
         mNoGroupString = getContext().getString(R.string.group_edit_field_hint_text);
+        setFocusable(true);
+        setFocusableInTouchMode(true);
     }
 
     @Override
@@ -318,6 +320,7 @@
             return;
         }
 
+        requestFocus();
         mAdapter = new GroupMembershipAdapter<GroupSelectionItem>(
                 getContext(), R.layout.group_membership_list_item);
 
diff --git a/tests/src/com/android/contacts/common/model/DeviceLocalAccountLocatorTests.java b/tests/src/com/android/contacts/common/model/DeviceLocalAccountLocatorTests.java
index e03c3e5..f1a2714 100644
--- a/tests/src/com/android/contacts/common/model/DeviceLocalAccountLocatorTests.java
+++ b/tests/src/com/android/contacts/common/model/DeviceLocalAccountLocatorTests.java
@@ -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.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 @@
         // 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 @@
         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 @@
         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 @@
 
     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 @@
             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) {
-            return mNextQueryResult;
+            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);
         }
     }
 }