summaryrefslogtreecommitdiff
path: root/java/com/android
diff options
context:
space:
mode:
authorcalderwoodra <calderwoodra@google.com>2017-08-08 20:49:57 -0700
committerEric Erfanian <erfanian@google.com>2017-08-09 17:34:10 -0700
commit0b2626e433091f8ca75ab7018289ea8ec0ec8bbe (patch)
tree0c9cabbccb92841f779cf03082b651f463d28a1a /java/com/android
parentd021f7a61ae8debee8e2b3ca69a6aee99be41753 (diff)
Added remote directories to the new search fragment.
When Dialer users search for contacts, if they have an enterprise account on their device, they can also search for enterprise/remote contacts. This change adds the directory queries/results to the new search fragment. screenshot: http://screen/S9mpsvnwtCv Bug: 37209462 Test: javatests/.../searchfragment/remote PiperOrigin-RevId: 164681686 Change-Id: I88bc5bceb4c745d8f6f7d9651929d49100283756
Diffstat (limited to 'java/com/android')
-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