summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java1
-rw-r--r--java/com/android/dialer/searchfragment/list/NewSearchFragment.java66
-rw-r--r--java/com/android/dialer/searchfragment/list/SearchAdapter.java22
-rw-r--r--java/com/android/dialer/searchfragment/list/SearchCursorManager.java23
-rw-r--r--java/com/android/dialer/searchfragment/list/res/layout/header_layout.xml1
-rw-r--r--java/com/android/dialer/searchfragment/remote/AndroidManifest.xml16
-rw-r--r--java/com/android/dialer/searchfragment/remote/RemoteContactViewHolder.java19
-rw-r--r--java/com/android/dialer/searchfragment/remote/RemoteContactsCursor.java105
-rw-r--r--java/com/android/dialer/searchfragment/remote/RemoteContactsCursorLoader.java63
-rw-r--r--java/com/android/dialer/searchfragment/remote/RemoteDirectoriesCursorLoader.java6
-rw-r--r--java/com/android/dialer/searchfragment/remote/res/values/strings.xml20
11 files changed, 299 insertions, 43 deletions
diff --git a/java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java b/java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java
index 2bd9cdd8a..1e8224ddb 100644
--- a/java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java
+++ b/java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java
@@ -110,6 +110,7 @@ public final class SearchContactViewHolder extends ViewHolder implements OnClick
}
}
+ // Show the contact photo next to only the first number if a contact has multiple numbers
private boolean shouldShowPhoto(SearchCursor cursor) {
int currentPosition = cursor.getPosition();
String currentLookupKey = cursor.getString(Projections.PHONE_LOOKUP_KEY);
diff --git a/java/com/android/dialer/searchfragment/list/NewSearchFragment.java b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java
index 1a489513c..2c0281536 100644
--- a/java/com/android/dialer/searchfragment/list/NewSearchFragment.java
+++ b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java
@@ -40,11 +40,16 @@ import com.android.dialer.common.concurrent.ThreadUtil;
import com.android.dialer.searchfragment.common.SearchCursor;
import com.android.dialer.searchfragment.cp2.SearchContactsCursorLoader;
import com.android.dialer.searchfragment.nearbyplaces.NearbyPlacesCursorLoader;
+import com.android.dialer.searchfragment.remote.RemoteContactsCursorLoader;
+import com.android.dialer.searchfragment.remote.RemoteDirectoriesCursorLoader;
+import com.android.dialer.searchfragment.remote.RemoteDirectoriesCursorLoader.Directory;
import com.android.dialer.util.PermissionsUtil;
import com.android.dialer.util.ViewUtil;
import com.android.dialer.widget.EmptyContentView;
import com.android.dialer.widget.EmptyContentView.OnEmptyViewActionButtonClickedListener;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
/** Fragment used for searching contacts. */
public final class NewSearchFragment extends Fragment
@@ -57,15 +62,21 @@ public final class NewSearchFragment extends Fragment
@VisibleForTesting public static final int READ_CONTACTS_PERMISSION_REQUEST_CODE = 1;
private static final int CONTACTS_LOADER_ID = 0;
- private static final int NEARBY_PLACES_ID = 1;
+ private static final int NEARBY_PLACES_LOADER_ID = 1;
+ private static final int REMOTE_DIRECTORIES_LOADER_ID = 2;
+ private static final int REMOTE_CONTACTS_LOADER_ID = 3;
private EmptyContentView emptyContentView;
private RecyclerView recyclerView;
private SearchAdapter adapter;
private String query;
+ private boolean remoteDirectoriesDisabledForTesting;
+ private final List<Directory> directories = new ArrayList<>();
private final Runnable loadNearbyPlacesRunnable =
- () -> getLoaderManager().restartLoader(NEARBY_PLACES_ID, null, this);
+ () -> getLoaderManager().restartLoader(NEARBY_PLACES_LOADER_ID, null, this);
+ private final Runnable loadRemoteContactsRunnable =
+ () -> getLoaderManager().restartLoader(REMOTE_CONTACTS_LOADER_ID, null, this);
private Runnable updatePositionRunnable;
@@ -99,6 +110,7 @@ public final class NewSearchFragment extends Fragment
private void initLoaders() {
getLoaderManager().initLoader(CONTACTS_LOADER_ID, null, this);
loadNearbyPlacesCursor();
+ loadRemoteDirectoriesCursor();
}
@Override
@@ -106,8 +118,12 @@ public final class NewSearchFragment extends Fragment
// TODO(calderwoodra) add enterprise loader
if (id == CONTACTS_LOADER_ID) {
return new SearchContactsCursorLoader(getContext());
- } else if (id == NEARBY_PLACES_ID) {
+ } else if (id == NEARBY_PLACES_LOADER_ID) {
return new NearbyPlacesCursorLoader(getContext(), query);
+ } else if (id == REMOTE_DIRECTORIES_LOADER_ID) {
+ return new RemoteDirectoriesCursorLoader(getContext());
+ } else if (id == REMOTE_CONTACTS_LOADER_ID) {
+ return new RemoteContactsCursorLoader(getContext(), query, directories);
} else {
throw new IllegalStateException("Invalid loader id: " + id);
}
@@ -115,14 +131,29 @@ public final class NewSearchFragment extends Fragment
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
- if (!(cursor instanceof SearchCursor)) {
+ if (cursor != null
+ && !(loader instanceof RemoteDirectoriesCursorLoader)
+ && !(cursor instanceof SearchCursor)) {
throw Assert.createIllegalStateFailException("Cursors must implement SearchCursor");
}
if (loader instanceof SearchContactsCursorLoader) {
adapter.setContactsCursor((SearchCursor) cursor);
+
} else if (loader instanceof NearbyPlacesCursorLoader) {
adapter.setNearbyPlacesCursor((SearchCursor) cursor);
+
+ } else if (loader instanceof RemoteContactsCursorLoader) {
+ adapter.setRemoteContactsCursor((SearchCursor) cursor);
+
+ } else if (loader instanceof RemoteDirectoriesCursorLoader) {
+ directories.clear();
+ cursor.moveToPosition(-1);
+ while (cursor.moveToNext()) {
+ directories.add(RemoteDirectoriesCursorLoader.readDirectory(cursor));
+ }
+ loadRemoteContactsCursors();
+
} else {
throw new IllegalStateException("Invalid loader: " + loader);
}
@@ -139,6 +170,7 @@ public final class NewSearchFragment extends Fragment
if (adapter != null) {
adapter.setQuery(query);
loadNearbyPlacesCursor();
+ loadRemoteContactsCursors();
}
}
@@ -159,6 +191,7 @@ public final class NewSearchFragment extends Fragment
public void onDestroy() {
super.onDestroy();
ThreadUtil.getUiThreadHandler().removeCallbacks(loadNearbyPlacesRunnable);
+ ThreadUtil.getUiThreadHandler().removeCallbacks(loadRemoteContactsRunnable);
}
private void loadNearbyPlacesCursor() {
@@ -198,4 +231,29 @@ public final class NewSearchFragment extends Fragment
this, deniedPermissions, READ_CONTACTS_PERMISSION_REQUEST_CODE);
}
}
+
+ private void loadRemoteDirectoriesCursor() {
+ if (!remoteDirectoriesDisabledForTesting) {
+ getLoaderManager().initLoader(REMOTE_DIRECTORIES_LOADER_ID, null, this);
+ }
+ }
+
+ private void loadRemoteContactsCursors() {
+ if (remoteDirectoriesDisabledForTesting) {
+ return;
+ }
+
+ // Cancel existing load if one exists.
+ ThreadUtil.getUiThreadHandler().removeCallbacks(loadRemoteContactsRunnable);
+ ThreadUtil.getUiThreadHandler()
+ .postDelayed(loadRemoteContactsRunnable, NETWORK_SEARCH_DELAY_MILLIS);
+ }
+
+ // Currently, setting up multiple FakeContentProviders doesn't work and results in this fragment
+ // being untestable while it can query multiple datasources. This is a temporary fix.
+ // TODO(b/64099602): Remove this method and test this fragment with multiple data sources
+ @VisibleForTesting
+ public void setRemoteDirectoriesDisabled(boolean disabled) {
+ remoteDirectoriesDisabledForTesting = disabled;
+ }
}
diff --git a/java/com/android/dialer/searchfragment/list/SearchAdapter.java b/java/com/android/dialer/searchfragment/list/SearchAdapter.java
index c8588fc7d..81e8e38f7 100644
--- a/java/com/android/dialer/searchfragment/list/SearchAdapter.java
+++ b/java/com/android/dialer/searchfragment/list/SearchAdapter.java
@@ -26,6 +26,7 @@ import com.android.dialer.searchfragment.common.SearchCursor;
import com.android.dialer.searchfragment.cp2.SearchContactViewHolder;
import com.android.dialer.searchfragment.list.SearchCursorManager.RowType;
import com.android.dialer.searchfragment.nearbyplaces.NearbyPlaceViewHolder;
+import com.android.dialer.searchfragment.remote.RemoteContactViewHolder;
/** RecyclerView adapter for {@link NewSearchFragment}. */
class SearchAdapter extends RecyclerView.Adapter<ViewHolder> {
@@ -54,7 +55,9 @@ class SearchAdapter extends RecyclerView.Adapter<ViewHolder> {
case RowType.NEARBY_PLACES_HEADER:
return new HeaderViewHolder(
LayoutInflater.from(context).inflate(R.layout.header_layout, root, false));
- case RowType.DIRECTORY_ROW: // TODO(calderwoodra): add directory rows to search
+ case RowType.DIRECTORY_ROW:
+ return new RemoteContactViewHolder(
+ LayoutInflater.from(context).inflate(R.layout.search_contact_row, root, false));
case RowType.INVALID:
default:
throw Assert.createIllegalStateFailException("Invalid RowType: " + rowType);
@@ -72,8 +75,12 @@ class SearchAdapter extends RecyclerView.Adapter<ViewHolder> {
((SearchContactViewHolder) holder).bind(searchCursorManager.getCursor(position), query);
} else if (holder instanceof NearbyPlaceViewHolder) {
((NearbyPlaceViewHolder) holder).bind(searchCursorManager.getCursor(position), query);
+ } else if (holder instanceof RemoteContactViewHolder) {
+ ((RemoteContactViewHolder) holder).bind(searchCursorManager.getCursor(position), query);
} else if (holder instanceof HeaderViewHolder) {
- ((HeaderViewHolder) holder).setHeader(searchCursorManager.getHeaderText(position));
+ String header =
+ searchCursorManager.getCursor(position).getString(SearchCursor.HEADER_TEXT_POSITION);
+ ((HeaderViewHolder) holder).setHeader(header);
} else {
throw Assert.createIllegalStateFailException("Invalid ViewHolder: " + holder);
}
@@ -101,7 +108,14 @@ class SearchAdapter extends RecyclerView.Adapter<ViewHolder> {
}
public void setNearbyPlacesCursor(SearchCursor nearbyPlacesCursor) {
- searchCursorManager.setNearbyPlacesCursor(nearbyPlacesCursor);
- notifyDataSetChanged();
+ if (searchCursorManager.setNearbyPlacesCursor(nearbyPlacesCursor)) {
+ notifyDataSetChanged();
+ }
+ }
+
+ public void setRemoteContactsCursor(SearchCursor remoteContactsCursor) {
+ if (searchCursorManager.setCorpDirectoryCursor(remoteContactsCursor)) {
+ notifyDataSetChanged();
+ }
}
}
diff --git a/java/com/android/dialer/searchfragment/list/SearchCursorManager.java b/java/com/android/dialer/searchfragment/list/SearchCursorManager.java
index 68f770af9..b385aa392 100644
--- a/java/com/android/dialer/searchfragment/list/SearchCursorManager.java
+++ b/java/com/android/dialer/searchfragment/list/SearchCursorManager.java
@@ -57,6 +57,7 @@ final class SearchCursorManager {
})
@interface RowType {
int INVALID = 0;
+ // TODO(calderwoodra) add suggestions header and list
/** Header to mark the start of contact rows. */
int CONTACT_HEADER = 1;
/** A row containing contact information for contacts stored locally on device. */
@@ -75,9 +76,10 @@ final class SearchCursorManager {
private SearchCursor nearbyPlacesCursor = null;
private SearchCursor corpDirectoryCursor = null;
- void setContactsCursor(SearchCursor cursor) {
+ /** Returns true if the cursor changed. */
+ boolean setContactsCursor(SearchCursor cursor) {
if (cursor == contactsCursor) {
- return;
+ return false;
}
if (contactsCursor != null && !contactsCursor.isClosed()) {
@@ -89,11 +91,13 @@ final class SearchCursorManager {
} else {
contactsCursor = null;
}
+ return true;
}
- void setNearbyPlacesCursor(SearchCursor cursor) {
+ /** Returns true if the cursor changed. */
+ boolean setNearbyPlacesCursor(SearchCursor cursor) {
if (cursor == nearbyPlacesCursor) {
- return;
+ return false;
}
if (nearbyPlacesCursor != null && !nearbyPlacesCursor.isClosed()) {
@@ -105,11 +109,13 @@ final class SearchCursorManager {
} else {
nearbyPlacesCursor = null;
}
+ return true;
}
- void setCorpDirectoryCursor(SearchCursor cursor) {
+ /** Returns true if a cursor changed. */
+ boolean setCorpDirectoryCursor(SearchCursor cursor) {
if (cursor == corpDirectoryCursor) {
- return;
+ return false;
}
if (corpDirectoryCursor != null && !corpDirectoryCursor.isClosed()) {
@@ -121,6 +127,7 @@ final class SearchCursorManager {
} else {
corpDirectoryCursor = null;
}
+ return true;
}
boolean setQuery(String query) {
@@ -216,10 +223,6 @@ final class SearchCursorManager {
throw Assert.createIllegalStateFailException("No valid cursor.");
}
- String getHeaderText(int position) {
- return getCursor(position).getString(SearchCursor.HEADER_TEXT_POSITION);
- }
-
/** removes all cursors. */
void clear() {
if (contactsCursor != null) {
diff --git a/java/com/android/dialer/searchfragment/list/res/layout/header_layout.xml b/java/com/android/dialer/searchfragment/list/res/layout/header_layout.xml
index 36af42ed9..eef0dee94 100644
--- a/java/com/android/dialer/searchfragment/list/res/layout/header_layout.xml
+++ b/java/com/android/dialer/searchfragment/list/res/layout/header_layout.xml
@@ -18,5 +18,6 @@
android:id="@+id/header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
android:paddingStart="16dp"
style="@style/SecondaryText"/>
diff --git a/java/com/android/dialer/searchfragment/remote/AndroidManifest.xml b/java/com/android/dialer/searchfragment/remote/AndroidManifest.xml
new file mode 100644
index 000000000..e52f5319e
--- /dev/null
+++ b/java/com/android/dialer/searchfragment/remote/AndroidManifest.xml
@@ -0,0 +1,16 @@
+<!--
+ ~ Copyright (C) 2017 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
+ -->
+<manifest package="com.android.dialer.searchfragment.remote"/> \ No newline at end of file
diff --git a/java/com/android/dialer/searchfragment/remote/RemoteContactViewHolder.java b/java/com/android/dialer/searchfragment/remote/RemoteContactViewHolder.java
index 18a871814..5fb12d349 100644
--- a/java/com/android/dialer/searchfragment/remote/RemoteContactViewHolder.java
+++ b/java/com/android/dialer/searchfragment/remote/RemoteContactViewHolder.java
@@ -34,6 +34,7 @@ import com.android.dialer.lettertile.LetterTileDrawable;
import com.android.dialer.searchfragment.common.Projections;
import com.android.dialer.searchfragment.common.QueryBoldingUtil;
import com.android.dialer.searchfragment.common.R;
+import com.android.dialer.searchfragment.common.SearchCursor;
import com.android.dialer.telecom.TelecomUtil;
/** ViewHolder for a nearby place row. */
@@ -60,7 +61,7 @@ public final class RemoteContactViewHolder extends RecyclerView.ViewHolder
* Binds the ViewHolder with a cursor from {@link RemoteContactsCursorLoader} with the data found
* at the cursors current position.
*/
- public void bind(Cursor cursor, String query) {
+ public void bind(SearchCursor cursor, String query) {
number = cursor.getString(Projections.PHONE_NUMBER);
String name = cursor.getString(Projections.PHONE_DISPLAY_NAME);
String label = getLabel(context.getResources(), cursor);
@@ -91,17 +92,19 @@ public final class RemoteContactViewHolder extends RecyclerView.ViewHolder
}
}
- private boolean shouldShowPhoto(Cursor cursor) {
+ // Show the contact photo next to only the first number if a contact has multiple numbers
+ private boolean shouldShowPhoto(SearchCursor cursor) {
int currentPosition = cursor.getPosition();
- if (currentPosition == 0) {
- return true;
- }
-
String currentLookupKey = cursor.getString(Projections.PHONE_LOOKUP_KEY);
cursor.moveToPosition(currentPosition - 1);
- String previousLookupKey = cursor.getString(Projections.PHONE_LOOKUP_KEY);
+
+ if (!cursor.isHeader() && !cursor.isBeforeFirst()) {
+ String previousLookupKey = cursor.getString(Projections.PHONE_LOOKUP_KEY);
+ cursor.moveToPosition(currentPosition);
+ return !currentLookupKey.equals(previousLookupKey);
+ }
cursor.moveToPosition(currentPosition);
- return !currentLookupKey.equals(previousLookupKey);
+ return true;
}
// TODO(calderwoodra): unify this into a utility method with CallLogAdapter#getNumberType
diff --git a/java/com/android/dialer/searchfragment/remote/RemoteContactsCursor.java b/java/com/android/dialer/searchfragment/remote/RemoteContactsCursor.java
new file mode 100644
index 000000000..d7c4f3805
--- /dev/null
+++ b/java/com/android/dialer/searchfragment/remote/RemoteContactsCursor.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2017 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.dialer.searchfragment.remote;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.database.MergeCursor;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import com.android.dialer.common.Assert;
+import com.android.dialer.searchfragment.common.SearchCursor;
+import com.android.dialer.searchfragment.remote.RemoteDirectoriesCursorLoader.Directory;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * {@link MergeCursor} used for combining remote directory cursors into one cursor.
+ *
+ * <p>Usually a device with multiple Google accounts will have multiple remote directories returned
+ * by {@link RemoteDirectoriesCursorLoader}, each represented as a {@link Directory}.
+ *
+ * <p>This cursor merges them together with a header at the start of each cursor/list using {@link
+ * Directory#getDisplayName()} as the header text.
+ */
+@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+public final class RemoteContactsCursor extends MergeCursor implements SearchCursor {
+
+ /**
+ * Returns a single cursor with headers inserted between each non-empty cursor. If all cursors are
+ * empty, null or closed, this method returns null.
+ */
+ @Nullable
+ @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+ public static RemoteContactsCursor newInstance(
+ Context context, Cursor[] cursors, List<Directory> directories) {
+ Assert.checkArgument(
+ cursors.length == directories.size(), "Directories and cursors must be the same size.");
+ Cursor[] cursorsWithHeaders = insertHeaders(context, cursors, directories);
+ if (cursorsWithHeaders.length > 0) {
+ return new RemoteContactsCursor(cursorsWithHeaders);
+ }
+ return null;
+ }
+
+ private RemoteContactsCursor(Cursor[] cursors) {
+ super(cursors);
+ }
+
+ private static Cursor[] insertHeaders(
+ Context context, Cursor[] cursors, List<Directory> directories) {
+ List<Cursor> cursorList = new ArrayList<>();
+ for (int i = 0; i < cursors.length; i++) {
+ Cursor cursor = cursors[i];
+
+ if (cursor == null || cursor.isClosed()) {
+ continue;
+ }
+
+ Directory directory = directories.get(i);
+ if (cursor.getCount() == 0) {
+ // Since the cursor isn't being merged in, we need to close it here.
+ cursor.close();
+ continue;
+ }
+
+ cursorList.add(createHeaderCursor(context, directory.getDisplayName()));
+ cursorList.add(cursor);
+ }
+ return cursorList.toArray(new Cursor[cursorList.size()]);
+ }
+
+ private static MatrixCursor createHeaderCursor(Context context, String name) {
+ MatrixCursor headerCursor = new MatrixCursor(HEADER_PROJECTION, 1);
+ headerCursor.addRow(new String[] {context.getString(R.string.directory, name)});
+ return headerCursor;
+ }
+
+ /** Returns true if the current position is a header row. */
+ @Override
+ public boolean isHeader() {
+ return !isClosed() && getColumnIndex(HEADER_PROJECTION[HEADER_TEXT_POSITION]) != -1;
+ }
+
+ @Override
+ public boolean updateQuery(@Nullable String query) {
+ // When the query changes, a new network request is made for nearby places. Meaning this cursor
+ // will be closed and another created, so return false.
+ return false;
+ }
+}
diff --git a/java/com/android/dialer/searchfragment/remote/RemoteContactsCursorLoader.java b/java/com/android/dialer/searchfragment/remote/RemoteContactsCursorLoader.java
index c9cd7655d..771b7f183 100644
--- a/java/com/android/dialer/searchfragment/remote/RemoteContactsCursorLoader.java
+++ b/java/com/android/dialer/searchfragment/remote/RemoteContactsCursorLoader.java
@@ -18,17 +18,26 @@ package com.android.dialer.searchfragment.remote;
import android.content.Context;
import android.content.CursorLoader;
+import android.database.Cursor;
import android.net.Uri;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.support.annotation.VisibleForTesting;
+import android.text.TextUtils;
import com.android.dialer.searchfragment.common.Projections;
import com.android.dialer.searchfragment.remote.RemoteDirectoriesCursorLoader.Directory;
+import java.util.List;
-/** Cursor loader to load extended contacts on device. */
-final class RemoteContactsCursorLoader extends CursorLoader {
+/**
+ * Cursor loader to load extended contacts on device.
+ *
+ * <p>This loader performs several database queries in serial and merges the resulting cursors
+ * together into {@link RemoteContactsCursor}. If there are no results, the loader will return a
+ * null cursor.
+ */
+public final class RemoteContactsCursorLoader extends CursorLoader {
private static final Uri ENTERPRISE_CONTENT_FILTER_URI =
Uri.withAppendedPath(Phone.CONTENT_URI, "filter_enterprise");
@@ -36,25 +45,55 @@ final class RemoteContactsCursorLoader extends CursorLoader {
private static final String IGNORE_NUMBER_TOO_LONG_CLAUSE = "length(" + Phone.NUMBER + ") < 1000";
private static final String MAX_RESULTS = "20";
- private final Directory directory;
+ private final String query;
+ private final List<Directory> directories;
+ private final Cursor[] cursors;
- RemoteContactsCursorLoader(Context context, String query, Directory directory) {
+ public RemoteContactsCursorLoader(Context context, String query, List<Directory> directories) {
super(
context,
- getContentFilterUri(query, directory.getId()),
+ null,
Projections.PHONE_PROJECTION,
IGNORE_NUMBER_TOO_LONG_CLAUSE,
null,
Phone.SORT_KEY_PRIMARY);
- this.directory = directory;
+ this.query = query;
+ this.directories = directories;
+ cursors = new Cursor[directories.size()];
+ }
+
+ @Override
+ public Cursor loadInBackground() {
+ for (int i = 0; i < directories.size(); i++) {
+ Directory directory = directories.get(i);
+ // Since the on device contacts could be queried as remote directories and we already query
+ // them in SearchContactsCursorLoader, avoid querying them again.
+ // TODO(calderwoodra): It's a happy coincidence that on device contacts don't have directory
+ // names set, leaving this todo to investigate a better way to isolate them from other remote
+ // directories.
+ if (TextUtils.isEmpty(directory.getDisplayName())) {
+ cursors[i] = null;
+ continue;
+ }
+ cursors[i] =
+ getContext()
+ .getContentResolver()
+ .query(
+ getContentFilterUri(query, directory.getId()),
+ getProjection(),
+ getSelection(),
+ getSelectionArgs(),
+ getSortOrder());
+ }
+ return RemoteContactsCursor.newInstance(getContext(), cursors, directories);
}
@VisibleForTesting
static Uri getContentFilterUri(String query, int directoryId) {
- Uri baseUri = Phone.CONTENT_FILTER_URI;
- if (VERSION.SDK_INT >= VERSION_CODES.N) {
- baseUri = ENTERPRISE_CONTENT_FILTER_URI;
- }
+ Uri baseUri =
+ VERSION.SDK_INT >= VERSION_CODES.N
+ ? ENTERPRISE_CONTENT_FILTER_URI
+ : Phone.CONTENT_FILTER_URI;
return baseUri
.buildUpon()
@@ -64,8 +103,4 @@ final class RemoteContactsCursorLoader extends CursorLoader {
.appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY, MAX_RESULTS)
.build();
}
-
- public Directory getDirectory() {
- return directory;
- }
}
diff --git a/java/com/android/dialer/searchfragment/remote/RemoteDirectoriesCursorLoader.java b/java/com/android/dialer/searchfragment/remote/RemoteDirectoriesCursorLoader.java
index 630c73cd4..327a62c7b 100644
--- a/java/com/android/dialer/searchfragment/remote/RemoteDirectoriesCursorLoader.java
+++ b/java/com/android/dialer/searchfragment/remote/RemoteDirectoriesCursorLoader.java
@@ -44,12 +44,12 @@ public final class RemoteDirectoriesCursorLoader extends CursorLoader {
ContactsContract.Directory.PHOTO_SUPPORT,
};
- RemoteDirectoriesCursorLoader(Context context) {
+ public RemoteDirectoriesCursorLoader(Context context) {
super(context, getContentUri(), PROJECTION, null, null, ContactsContract.Directory._ID);
}
/** @return current cursor row represented as a {@link Directory}. */
- static Directory readDirectory(Cursor cursor) {
+ public static Directory readDirectory(Cursor cursor) {
return Directory.create(
cursor.getInt(ID), cursor.getString(DISPLAY_NAME), cursor.getInt(PHOTO_SUPPORT) != 0);
}
@@ -63,7 +63,7 @@ public final class RemoteDirectoriesCursorLoader extends CursorLoader {
/** POJO representing the results returned from {@link RemoteDirectoriesCursorLoader}. */
@AutoValue
public abstract static class Directory {
- static Directory create(int id, @Nullable String displayName, boolean supportsPhotos) {
+ public static Directory create(int id, @Nullable String displayName, boolean supportsPhotos) {
return new AutoValue_RemoteDirectoriesCursorLoader_Directory(id, displayName, supportsPhotos);
}
diff --git a/java/com/android/dialer/searchfragment/remote/res/values/strings.xml b/java/com/android/dialer/searchfragment/remote/res/values/strings.xml
new file mode 100644
index 000000000..beabba135
--- /dev/null
+++ b/java/com/android/dialer/searchfragment/remote/res/values/strings.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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
+ -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Label for a list of contacts stored in a seperate directory [CHAR LIMIT=30]-->
+ <string name="directory">Directory <xliff:g example="google.com" id="email">%1$s</xliff:g></string>
+</resources> \ No newline at end of file