From 844dcad13ad494256dddfdc44f7ad90a5f93c530 Mon Sep 17 00:00:00 2001 From: linyuh Date: Tue, 20 Feb 2018 13:40:18 -0800 Subject: Merge searchfragment/remote into searchfragment/directories and renaming things accordingly. Test: Existing tests PiperOrigin-RevId: 186355284 Change-Id: I7c2a2917175ef13742ca3b2e628d8655dc668f60 --- .../directories/DirectoriesCursorLoader.java | 7 +- .../directories/DirectoryContactViewHolder.java | 145 ++++++++++++++++++ .../directories/DirectoryContactsCursor.java | 153 +++++++++++++++++++ .../directories/DirectoryContactsCursorLoader.java | 162 +++++++++++++++++++++ .../directories/res/values/strings.xml | 20 +++ .../searchfragment/list/NewSearchFragment.java | 71 +++++---- .../dialer/searchfragment/list/SearchAdapter.java | 12 +- .../searchfragment/remote/AndroidManifest.xml | 16 -- .../remote/RemoteContactViewHolder.java | 145 ------------------ .../remote/RemoteContactsCursor.java | 154 -------------------- .../remote/RemoteContactsCursorLoader.java | 162 --------------------- .../searchfragment/remote/res/values/strings.xml | 20 --- 12 files changed, 534 insertions(+), 533 deletions(-) create mode 100644 java/com/android/dialer/searchfragment/directories/DirectoryContactViewHolder.java create mode 100644 java/com/android/dialer/searchfragment/directories/DirectoryContactsCursor.java create mode 100644 java/com/android/dialer/searchfragment/directories/DirectoryContactsCursorLoader.java create mode 100644 java/com/android/dialer/searchfragment/directories/res/values/strings.xml delete mode 100644 java/com/android/dialer/searchfragment/remote/AndroidManifest.xml delete mode 100644 java/com/android/dialer/searchfragment/remote/RemoteContactViewHolder.java delete mode 100644 java/com/android/dialer/searchfragment/remote/RemoteContactsCursor.java delete mode 100644 java/com/android/dialer/searchfragment/remote/RemoteContactsCursorLoader.java delete mode 100644 java/com/android/dialer/searchfragment/remote/res/values/strings.xml (limited to 'java/com/android/dialer/searchfragment') diff --git a/java/com/android/dialer/searchfragment/directories/DirectoriesCursorLoader.java b/java/com/android/dialer/searchfragment/directories/DirectoriesCursorLoader.java index 39c1187a4..dbe11dd96 100644 --- a/java/com/android/dialer/searchfragment/directories/DirectoriesCursorLoader.java +++ b/java/com/android/dialer/searchfragment/directories/DirectoriesCursorLoader.java @@ -31,7 +31,12 @@ import com.google.auto.value.AutoValue; import java.util.ArrayList; import java.util.List; -/** {@link CursorLoader} to load the list of all directories (local and remote). */ +/** + * {@link CursorLoader} to load information about all directories (local and remote). + * + *

Information about a directory includes its ID, display name, etc, but doesn't include the + * contacts in it. + */ public final class DirectoriesCursorLoader extends CursorLoader { public static final String[] PROJECTION = { diff --git a/java/com/android/dialer/searchfragment/directories/DirectoryContactViewHolder.java b/java/com/android/dialer/searchfragment/directories/DirectoryContactViewHolder.java new file mode 100644 index 000000000..ff321fc75 --- /dev/null +++ b/java/com/android/dialer/searchfragment/directories/DirectoryContactViewHolder.java @@ -0,0 +1,145 @@ +/* + * 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.directories; + +import android.content.Context; +import android.content.res.Resources; +import android.database.Cursor; +import android.net.Uri; +import android.provider.ContactsContract; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.provider.ContactsContract.Contacts; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.view.View; +import android.widget.ImageView; +import android.widget.QuickContactBadge; +import android.widget.TextView; +import com.android.contacts.common.compat.DirectoryCompat; +import com.android.dialer.callintent.CallInitiationType; +import com.android.dialer.callintent.CallIntentBuilder; +import com.android.dialer.contactphoto.ContactPhotoManager; +import com.android.dialer.lettertile.LetterTileDrawable; +import com.android.dialer.precall.PreCall; +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; + +/** ViewHolder for a directory contact row. */ +public final class DirectoryContactViewHolder extends RecyclerView.ViewHolder + implements View.OnClickListener { + + private final Context context; + private final TextView nameView; + private final TextView numberView; + private final QuickContactBadge photo; + private final ImageView workBadge; + + private String number; + + public DirectoryContactViewHolder(View view) { + super(view); + view.setOnClickListener(this); + photo = view.findViewById(R.id.photo); + nameView = view.findViewById(R.id.primary); + numberView = view.findViewById(R.id.secondary); + workBadge = view.findViewById(R.id.work_icon); + context = view.getContext(); + } + + /** + * Binds the ViewHolder with a cursor from {@link DirectoryContactsCursorLoader} with the data + * found at the cursors current position. + */ + public void bind(SearchCursor cursor, String query) { + number = cursor.getString(Projections.PHONE_NUMBER); + String name = cursor.getString(Projections.DISPLAY_NAME); + String label = getLabel(context.getResources(), cursor); + String secondaryInfo = + TextUtils.isEmpty(label) + ? number + : context.getString( + com.android.contacts.common.R.string.call_subject_type_and_number, label, number); + + nameView.setText(QueryBoldingUtil.getNameWithQueryBolded(query, name, context)); + numberView.setText(QueryBoldingUtil.getNameWithQueryBolded(query, secondaryInfo, context)); + workBadge.setVisibility( + DirectoryCompat.isOnlyEnterpriseDirectoryId(cursor.getDirectoryId()) + ? View.VISIBLE + : View.GONE); + + if (shouldShowPhoto(cursor)) { + nameView.setVisibility(View.VISIBLE); + photo.setVisibility(View.VISIBLE); + String photoUri = cursor.getString(Projections.PHOTO_URI); + ContactPhotoManager.getInstance(context) + .loadDialerThumbnailOrPhoto( + photo, + getContactUri(cursor), + cursor.getLong(Projections.PHOTO_ID), + photoUri == null ? null : Uri.parse(photoUri), + name, + LetterTileDrawable.TYPE_DEFAULT); + } else { + nameView.setVisibility(View.GONE); + photo.setVisibility(View.INVISIBLE); + } + } + + // 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.LOOKUP_KEY); + cursor.moveToPosition(currentPosition - 1); + + if (!cursor.isHeader() && !cursor.isBeforeFirst()) { + String previousLookupKey = cursor.getString(Projections.LOOKUP_KEY); + cursor.moveToPosition(currentPosition); + return !currentLookupKey.equals(previousLookupKey); + } + cursor.moveToPosition(currentPosition); + return true; + } + + // TODO(calderwoodra): unify this into a utility method with CallLogAdapter#getNumberType + private static String getLabel(Resources resources, Cursor cursor) { + int numberType = cursor.getInt(Projections.PHONE_TYPE); + String numberLabel = cursor.getString(Projections.PHONE_LABEL); + + // Returns empty label instead of "custom" if the custom label is empty. + if (numberType == Phone.TYPE_CUSTOM && TextUtils.isEmpty(numberLabel)) { + return ""; + } + return (String) Phone.getTypeLabel(resources, numberType, numberLabel); + } + + private static Uri getContactUri(SearchCursor cursor) { + long contactId = cursor.getLong(Projections.ID); + String lookupKey = cursor.getString(Projections.LOOKUP_KEY); + return Contacts.getLookupUri(contactId, lookupKey) + .buildUpon() + .appendQueryParameter( + ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(cursor.getDirectoryId())) + .build(); + } + + @Override + public void onClick(View v) { + PreCall.start(context, new CallIntentBuilder(number, CallInitiationType.Type.REGULAR_SEARCH)); + } +} diff --git a/java/com/android/dialer/searchfragment/directories/DirectoryContactsCursor.java b/java/com/android/dialer/searchfragment/directories/DirectoryContactsCursor.java new file mode 100644 index 000000000..bf0bdc057 --- /dev/null +++ b/java/com/android/dialer/searchfragment/directories/DirectoryContactsCursor.java @@ -0,0 +1,153 @@ +/* + * 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.directories; + +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.contacts.common.compat.DirectoryCompat; +import com.android.dialer.common.Assert; +import com.android.dialer.searchfragment.common.SearchCursor; +import com.android.dialer.searchfragment.directories.DirectoriesCursorLoader.Directory; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * {@link MergeCursor} used for combining directory cursors into one cursor. + * + *

Usually a device with multiple Google accounts will have multiple directories returned by + * {@link DirectoriesCursorLoader}, each represented as a {@link Directory}. + * + *

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 DirectoryContactsCursor extends MergeCursor implements SearchCursor { + + /** + * {@link SearchCursor#HEADER_PROJECTION} with {@link #COLUMN_DIRECTORY_ID} appended on the end. + * + *

This is needed to get the directoryId associated with each contact. directoryIds are needed + * to load the correct quick contact card. + */ + private static final String[] PROJECTION = buildProjection(); + + private static final String COLUMN_DIRECTORY_ID = "directory_id"; + + /** + * 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 DirectoryContactsCursor newInstance( + Context context, Cursor[] cursors, List directories) { + Assert.checkArgument( + cursors.length == directories.size(), + "Directories (%d) and cursors (%d) must be the same size.", + directories.size(), + cursors.length); + Cursor[] cursorsWithHeaders = insertHeaders(context, cursors, directories); + if (cursorsWithHeaders.length > 0) { + return new DirectoryContactsCursor(cursorsWithHeaders); + } + return null; + } + + private DirectoryContactsCursor(Cursor[] cursors) { + super(cursors); + } + + private static Cursor[] insertHeaders( + Context context, Cursor[] cursors, List directories) { + List 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(), directory.getId())); + cursorList.add(cursor); + } + return cursorList.toArray(new Cursor[cursorList.size()]); + } + + private static MatrixCursor createHeaderCursor(Context context, String name, long id) { + MatrixCursor headerCursor = new MatrixCursor(PROJECTION, 1); + if (DirectoryCompat.isOnlyEnterpriseDirectoryId(id)) { + headerCursor.addRow( + new Object[] {context.getString(R.string.directory_search_label_work), id}); + } else { + headerCursor.addRow(new Object[] {context.getString(R.string.directory, name), id}); + } + return headerCursor; + } + + private static String[] buildProjection() { + String[] projection = Arrays.copyOf(HEADER_PROJECTION, HEADER_PROJECTION.length + 1); + projection[projection.length - 1] = COLUMN_DIRECTORY_ID; + return projection; + } + + /** 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 long getDirectoryId() { + int position = getPosition(); + // proceed backwards until we reach the header row, which contains the directory ID. + while (moveToPrevious()) { + int columnIndex = getColumnIndex(COLUMN_DIRECTORY_ID); + if (columnIndex == -1) { + continue; + } + + int id = getInt(columnIndex); + if (id == -1) { + continue; + } + + // return the cursor to it's original position/state + moveToPosition(position); + return id; + } + throw Assert.createIllegalStateFailException("No directory id for contact at: " + position); + } + + @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/directories/DirectoryContactsCursorLoader.java b/java/com/android/dialer/searchfragment/directories/DirectoryContactsCursorLoader.java new file mode 100644 index 000000000..fc36f59bb --- /dev/null +++ b/java/com/android/dialer/searchfragment/directories/DirectoryContactsCursorLoader.java @@ -0,0 +1,162 @@ +/* + * 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.directories; + +import android.content.Context; +import android.content.CursorLoader; +import android.database.Cursor; +import android.database.MatrixCursor; +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.NonNull; +import android.support.annotation.VisibleForTesting; +import com.android.contacts.common.compat.DirectoryCompat; +import com.android.dialer.searchfragment.common.Projections; +import com.android.dialer.searchfragment.directories.DirectoriesCursorLoader.Directory; +import java.util.ArrayList; +import java.util.List; + +/** + * Cursor loader to load extended contacts on device. + * + *

This loader performs several database queries in serial and merges the resulting cursors + * together into {@link DirectoryContactsCursor}. If there are no results, the loader will return a + * null cursor. + */ +public final class DirectoryContactsCursorLoader extends CursorLoader { + + private static final Uri ENTERPRISE_CONTENT_FILTER_URI = + Uri.withAppendedPath(Phone.CONTENT_URI, "filter_enterprise"); + + private static final String IGNORE_NUMBER_TOO_LONG_CLAUSE = "length(" + Phone.NUMBER + ") < 1000"; + private static final String PHONE_NUMBER_NOT_NULL = Phone.NUMBER + " IS NOT NULL"; + private static final String MAX_RESULTS = "10"; + + private final String query; + private final List directories; + private final Cursor[] cursors; + + public DirectoryContactsCursorLoader(Context context, String query, List directories) { + super( + context, + null, + Projections.DATA_PROJECTION, + IGNORE_NUMBER_TOO_LONG_CLAUSE + " AND " + PHONE_NUMBER_NOT_NULL, + null, + Phone.SORT_KEY_PRIMARY); + this.query = query; + this.directories = new ArrayList<>(directories); + cursors = new Cursor[directories.size()]; + } + + @Override + public Cursor loadInBackground() { + for (int i = 0; i < directories.size(); i++) { + Directory directory = directories.get(i); + + // Only load contacts in the enterprise directory & remote directories. + if (!DirectoryCompat.isRemoteDirectoryId(directory.getId()) + && !DirectoryCompat.isEnterpriseDirectoryId(directory.getId())) { + cursors[i] = null; + continue; + } + + // Filter out invisible directories. + if (DirectoryCompat.isInvisibleDirectory(directory.getId())) { + cursors[i] = null; + continue; + } + + Cursor cursor = + getContext() + .getContentResolver() + .query( + getContentFilterUri(query, directory.getId()), + getProjection(), + getSelection(), + getSelectionArgs(), + getSortOrder()); + // Even though the cursor specifies "WHERE PHONE_NUMBER IS NOT NULL" the Blackberry Hub app's + // directory extension doesn't appear to respect it, and sometimes returns a null phone + // number. In this case just hide the row entirely. See a bug. + cursors[i] = createMatrixCursorFilteringNullNumbers(cursor); + } + return DirectoryContactsCursor.newInstance(getContext(), cursors, directories); + } + + private MatrixCursor createMatrixCursorFilteringNullNumbers(Cursor cursor) { + if (cursor == null) { + return null; + } + MatrixCursor matrixCursor = new MatrixCursor(cursor.getColumnNames()); + try { + if (cursor.moveToFirst()) { + do { + String number = cursor.getString(Projections.PHONE_NUMBER); + if (number == null) { + continue; + } + matrixCursor.addRow(objectArrayFromCursor(cursor)); + } while (cursor.moveToNext()); + } + } finally { + cursor.close(); + } + return matrixCursor; + } + + @NonNull + private static Object[] objectArrayFromCursor(@NonNull Cursor cursor) { + Object[] values = new Object[cursor.getColumnCount()]; + for (int i = 0; i < cursor.getColumnCount(); i++) { + int fieldType = cursor.getType(i); + if (fieldType == Cursor.FIELD_TYPE_BLOB) { + values[i] = cursor.getBlob(i); + } else if (fieldType == Cursor.FIELD_TYPE_FLOAT) { + values[i] = cursor.getDouble(i); + } else if (fieldType == Cursor.FIELD_TYPE_INTEGER) { + values[i] = cursor.getLong(i); + } else if (fieldType == Cursor.FIELD_TYPE_STRING) { + values[i] = cursor.getString(i); + } else if (fieldType == Cursor.FIELD_TYPE_NULL) { + values[i] = null; + } else { + throw new IllegalStateException("Unknown fieldType (" + fieldType + ") for column: " + i); + } + } + return values; + } + + @VisibleForTesting + static Uri getContentFilterUri(String query, long directoryId) { + Uri baseUri = + VERSION.SDK_INT >= VERSION_CODES.N + ? ENTERPRISE_CONTENT_FILTER_URI + : Phone.CONTENT_FILTER_URI; + + return baseUri + .buildUpon() + .appendPath(query) + .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(directoryId)) + .appendQueryParameter(ContactsContract.REMOVE_DUPLICATE_ENTRIES, "true") + .appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY, MAX_RESULTS) + .build(); + } +} diff --git a/java/com/android/dialer/searchfragment/directories/res/values/strings.xml b/java/com/android/dialer/searchfragment/directories/res/values/strings.xml new file mode 100644 index 000000000..beabba135 --- /dev/null +++ b/java/com/android/dialer/searchfragment/directories/res/values/strings.xml @@ -0,0 +1,20 @@ + + + + + Directory %1$s + \ No newline at end of file diff --git a/java/com/android/dialer/searchfragment/list/NewSearchFragment.java b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java index 2d45457d2..aff946206 100644 --- a/java/com/android/dialer/searchfragment/list/NewSearchFragment.java +++ b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java @@ -65,9 +65,9 @@ import com.android.dialer.searchfragment.common.SearchCursor; import com.android.dialer.searchfragment.cp2.SearchContactsCursorLoader; import com.android.dialer.searchfragment.directories.DirectoriesCursorLoader; import com.android.dialer.searchfragment.directories.DirectoriesCursorLoader.Directory; +import com.android.dialer.searchfragment.directories.DirectoryContactsCursorLoader; import com.android.dialer.searchfragment.list.SearchActionViewHolder.Action; import com.android.dialer.searchfragment.nearbyplaces.NearbyPlacesCursorLoader; -import com.android.dialer.searchfragment.remote.RemoteContactsCursorLoader; import com.android.dialer.storage.StorageComponent; import com.android.dialer.util.CallUtil; import com.android.dialer.util.DialerUtils; @@ -103,8 +103,11 @@ public final class NewSearchFragment extends Fragment private static final int CONTACTS_LOADER_ID = 0; 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; + + // ID for the loader that loads info about all directories (local & remote). + private static final int DIRECTORIES_LOADER_ID = 2; + + private static final int DIRECTORY_CONTACTS_LOADER_ID = 3; private static final String KEY_QUERY = "key_query"; private static final String KEY_CALL_INITIATION_TYPE = "key_call_initiation_type"; @@ -117,15 +120,17 @@ public final class NewSearchFragment extends Fragment // for actions to add contact or send sms. private String rawNumber; private CallInitiationType.Type callInitiationType = CallInitiationType.Type.UNKNOWN_INITIATION; - private boolean remoteDirectoriesDisabledForTesting; + private boolean directoriesDisabledForTesting; + // Information about all local & remote directories (including ID, display name, etc, but not + // the contacts in them). private final List directories = new ArrayList<>(); private final Runnable loaderCp2ContactsRunnable = () -> getLoaderManager().restartLoader(CONTACTS_LOADER_ID, null, this); private final Runnable loadNearbyPlacesRunnable = () -> getLoaderManager().restartLoader(NEARBY_PLACES_LOADER_ID, null, this); - private final Runnable loadRemoteContactsRunnable = - () -> getLoaderManager().restartLoader(REMOTE_CONTACTS_LOADER_ID, null, this); + private final Runnable loadDirectoryContactsRunnable = + () -> getLoaderManager().restartLoader(DIRECTORY_CONTACTS_LOADER_ID, null, this); private final Runnable capabilitiesUpdatedRunnable = () -> adapter.notifyDataSetChanged(); private Runnable updatePositionRunnable; @@ -184,7 +189,7 @@ public final class NewSearchFragment extends Fragment private void initLoaders() { getLoaderManager().initLoader(CONTACTS_LOADER_ID, null, this); - loadRemoteDirectoriesCursor(); + loadDirectoriesCursor(); } @Override @@ -201,10 +206,10 @@ public final class NewSearchFragment extends Fragment directoryIds.add(directory.getId()); } return new NearbyPlacesCursorLoader(getContext(), query, directoryIds); - } else if (id == REMOTE_DIRECTORIES_LOADER_ID) { + } else if (id == DIRECTORIES_LOADER_ID) { return new DirectoriesCursorLoader(getContext()); - } else if (id == REMOTE_CONTACTS_LOADER_ID) { - return new RemoteContactsCursorLoader(getContext(), query, directories); + } else if (id == DIRECTORY_CONTACTS_LOADER_ID) { + return new DirectoryContactsCursorLoader(getContext(), query, directories); } else { throw new IllegalStateException("Invalid loader id: " + id); } @@ -225,14 +230,14 @@ public final class NewSearchFragment extends Fragment } else if (loader instanceof NearbyPlacesCursorLoader) { adapter.setNearbyPlacesCursor((SearchCursor) cursor); - } else if (loader instanceof RemoteContactsCursorLoader) { - adapter.setRemoteContactsCursor((SearchCursor) cursor); + } else if (loader instanceof DirectoryContactsCursorLoader) { + adapter.setDirectoryContactsCursor((SearchCursor) cursor); } else if (loader instanceof DirectoriesCursorLoader) { directories.clear(); directories.addAll(DirectoriesCursorLoader.toDirectories(cursor)); loadNearbyPlacesCursor(); - loadRemoteContactsCursors(); + loadDirectoryContactsCursors(); } else { throw new IllegalStateException("Invalid loader: " + loader); @@ -246,8 +251,8 @@ public final class NewSearchFragment extends Fragment adapter.setContactsCursor(null); } else if (loader instanceof NearbyPlacesCursorLoader) { adapter.setNearbyPlacesCursor(null); - } else if (loader instanceof RemoteContactsCursorLoader) { - adapter.setRemoteContactsCursor(null); + } else if (loader instanceof DirectoryContactsCursorLoader) { + adapter.setDirectoryContactsCursor(null); } } @@ -264,7 +269,7 @@ public final class NewSearchFragment extends Fragment adapter.setZeroSuggestVisible(isRegularSearch()); loadCp2ContactsCursor(); loadNearbyPlacesCursor(); - loadRemoteContactsCursors(); + loadDirectoryContactsCursors(); } } @@ -306,7 +311,7 @@ public final class NewSearchFragment extends Fragment super.onDestroy(); ThreadUtil.getUiThreadHandler().removeCallbacks(loaderCp2ContactsRunnable); ThreadUtil.getUiThreadHandler().removeCallbacks(loadNearbyPlacesRunnable); - ThreadUtil.getUiThreadHandler().removeCallbacks(loadRemoteContactsRunnable); + ThreadUtil.getUiThreadHandler().removeCallbacks(loadDirectoryContactsRunnable); ThreadUtil.getUiThreadHandler().removeCallbacks(capabilitiesUpdatedRunnable); } @@ -342,23 +347,27 @@ public final class NewSearchFragment extends Fragment } } - // Loads remote directories. - private void loadRemoteDirectoriesCursor() { - if (!remoteDirectoriesDisabledForTesting) { - getLoaderManager().initLoader(REMOTE_DIRECTORIES_LOADER_ID, null, this); + /** Loads info about all directories (local & remote). */ + private void loadDirectoriesCursor() { + if (!directoriesDisabledForTesting) { + getLoaderManager().initLoader(DIRECTORIES_LOADER_ID, null, this); } } - // Should not be called before remote directories have finished loading. - private void loadRemoteContactsCursors() { - if (remoteDirectoriesDisabledForTesting) { + /** + * Loads contacts stored in directories. + * + *

Should not be called before finishing loading info about all directories (local & remote). + */ + private void loadDirectoryContactsCursors() { + if (directoriesDisabledForTesting) { return; } // Cancel existing load if one exists. - ThreadUtil.getUiThreadHandler().removeCallbacks(loadRemoteContactsRunnable); + ThreadUtil.getUiThreadHandler().removeCallbacks(loadDirectoryContactsRunnable); ThreadUtil.getUiThreadHandler() - .postDelayed(loadRemoteContactsRunnable, NETWORK_SEARCH_DELAY_MILLIS); + .postDelayed(loadDirectoryContactsRunnable, NETWORK_SEARCH_DELAY_MILLIS); } private void loadCp2ContactsCursor() { @@ -368,7 +377,11 @@ public final class NewSearchFragment extends Fragment .postDelayed(loaderCp2ContactsRunnable, NETWORK_SEARCH_DELAY_MILLIS); } - // Should not be called before remote directories (not contacts) have finished loading. + /** + * Loads nearby places. + * + *

Should not be called before finishing loading info about all directories (local and remote). + */ private void loadNearbyPlacesCursor() { if (!PermissionsUtil.hasLocationPermissions(getContext()) && !StorageComponent.get(getContext()) @@ -443,8 +456,8 @@ public final class NewSearchFragment extends Fragment // being untestable while it can query multiple datasources. This is a temporary fix. // TODO(a bug): Remove this method and test this fragment with multiple data sources @VisibleForTesting - public void setRemoteDirectoriesDisabled(boolean disabled) { - remoteDirectoriesDisabledForTesting = disabled; + public void setDirectoriesDisabled(boolean disabled) { + directoriesDisabledForTesting = disabled; } /** diff --git a/java/com/android/dialer/searchfragment/list/SearchAdapter.java b/java/com/android/dialer/searchfragment/list/SearchAdapter.java index 1681097bf..462426943 100644 --- a/java/com/android/dialer/searchfragment/list/SearchAdapter.java +++ b/java/com/android/dialer/searchfragment/list/SearchAdapter.java @@ -31,9 +31,9 @@ import com.android.dialer.common.Assert; import com.android.dialer.searchfragment.common.RowClickListener; import com.android.dialer.searchfragment.common.SearchCursor; import com.android.dialer.searchfragment.cp2.SearchContactViewHolder; +import com.android.dialer.searchfragment.directories.DirectoryContactViewHolder; import com.android.dialer.searchfragment.list.SearchCursorManager.RowType; import com.android.dialer.searchfragment.nearbyplaces.NearbyPlaceViewHolder; -import com.android.dialer.searchfragment.remote.RemoteContactViewHolder; import java.util.List; /** RecyclerView adapter for {@link NewSearchFragment}. */ @@ -77,7 +77,7 @@ public final class SearchAdapter extends RecyclerView.Adapter { return new HeaderViewHolder( LayoutInflater.from(context).inflate(R.layout.header_layout, root, false)); case RowType.DIRECTORY_ROW: - return new RemoteContactViewHolder( + return new DirectoryContactViewHolder( LayoutInflater.from(context).inflate(R.layout.search_contact_row, root, false)); case RowType.SEARCH_ACTION: return new SearchActionViewHolder( @@ -104,8 +104,8 @@ public final class SearchAdapter extends RecyclerView.Adapter { ((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 DirectoryContactViewHolder) { + ((DirectoryContactViewHolder) holder).bind(searchCursorManager.getCursor(position), query); } else if (holder instanceof HeaderViewHolder) { String header = searchCursorManager.getCursor(position).getString(SearchCursor.HEADER_TEXT_POSITION); @@ -200,8 +200,8 @@ public final class SearchAdapter extends RecyclerView.Adapter { } } - public void setRemoteContactsCursor(SearchCursor remoteContactsCursor) { - if (searchCursorManager.setCorpDirectoryCursor(remoteContactsCursor)) { + void setDirectoryContactsCursor(SearchCursor directoryContactsCursor) { + if (searchCursorManager.setCorpDirectoryCursor(directoryContactsCursor)) { notifyDataSetChanged(); } } diff --git a/java/com/android/dialer/searchfragment/remote/AndroidManifest.xml b/java/com/android/dialer/searchfragment/remote/AndroidManifest.xml deleted file mode 100644 index e52f5319e..000000000 --- a/java/com/android/dialer/searchfragment/remote/AndroidManifest.xml +++ /dev/null @@ -1,16 +0,0 @@ - - \ 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 deleted file mode 100644 index 4be96fe58..000000000 --- a/java/com/android/dialer/searchfragment/remote/RemoteContactViewHolder.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * 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.content.res.Resources; -import android.database.Cursor; -import android.net.Uri; -import android.provider.ContactsContract; -import android.provider.ContactsContract.CommonDataKinds.Phone; -import android.provider.ContactsContract.Contacts; -import android.support.v7.widget.RecyclerView; -import android.text.TextUtils; -import android.view.View; -import android.widget.ImageView; -import android.widget.QuickContactBadge; -import android.widget.TextView; -import com.android.contacts.common.compat.DirectoryCompat; -import com.android.dialer.callintent.CallInitiationType; -import com.android.dialer.callintent.CallIntentBuilder; -import com.android.dialer.contactphoto.ContactPhotoManager; -import com.android.dialer.lettertile.LetterTileDrawable; -import com.android.dialer.precall.PreCall; -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; - -/** ViewHolder for a nearby place row. */ -public final class RemoteContactViewHolder extends RecyclerView.ViewHolder - implements View.OnClickListener { - - private final Context context; - private final TextView nameView; - private final TextView numberView; - private final QuickContactBadge photo; - private final ImageView workBadge; - - private String number; - - public RemoteContactViewHolder(View view) { - super(view); - view.setOnClickListener(this); - photo = view.findViewById(R.id.photo); - nameView = view.findViewById(R.id.primary); - numberView = view.findViewById(R.id.secondary); - workBadge = view.findViewById(R.id.work_icon); - context = view.getContext(); - } - - /** - * Binds the ViewHolder with a cursor from {@link RemoteContactsCursorLoader} with the data found - * at the cursors current position. - */ - public void bind(SearchCursor cursor, String query) { - number = cursor.getString(Projections.PHONE_NUMBER); - String name = cursor.getString(Projections.DISPLAY_NAME); - String label = getLabel(context.getResources(), cursor); - String secondaryInfo = - TextUtils.isEmpty(label) - ? number - : context.getString( - com.android.contacts.common.R.string.call_subject_type_and_number, label, number); - - nameView.setText(QueryBoldingUtil.getNameWithQueryBolded(query, name, context)); - numberView.setText(QueryBoldingUtil.getNameWithQueryBolded(query, secondaryInfo, context)); - workBadge.setVisibility( - DirectoryCompat.isOnlyEnterpriseDirectoryId(cursor.getDirectoryId()) - ? View.VISIBLE - : View.GONE); - - if (shouldShowPhoto(cursor)) { - nameView.setVisibility(View.VISIBLE); - photo.setVisibility(View.VISIBLE); - String photoUri = cursor.getString(Projections.PHOTO_URI); - ContactPhotoManager.getInstance(context) - .loadDialerThumbnailOrPhoto( - photo, - getContactUri(cursor), - cursor.getLong(Projections.PHOTO_ID), - photoUri == null ? null : Uri.parse(photoUri), - name, - LetterTileDrawable.TYPE_DEFAULT); - } else { - nameView.setVisibility(View.GONE); - photo.setVisibility(View.INVISIBLE); - } - } - - // 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.LOOKUP_KEY); - cursor.moveToPosition(currentPosition - 1); - - if (!cursor.isHeader() && !cursor.isBeforeFirst()) { - String previousLookupKey = cursor.getString(Projections.LOOKUP_KEY); - cursor.moveToPosition(currentPosition); - return !currentLookupKey.equals(previousLookupKey); - } - cursor.moveToPosition(currentPosition); - return true; - } - - // TODO(calderwoodra): unify this into a utility method with CallLogAdapter#getNumberType - private static String getLabel(Resources resources, Cursor cursor) { - int numberType = cursor.getInt(Projections.PHONE_TYPE); - String numberLabel = cursor.getString(Projections.PHONE_LABEL); - - // Returns empty label instead of "custom" if the custom label is empty. - if (numberType == Phone.TYPE_CUSTOM && TextUtils.isEmpty(numberLabel)) { - return ""; - } - return (String) Phone.getTypeLabel(resources, numberType, numberLabel); - } - - private static Uri getContactUri(SearchCursor cursor) { - long contactId = cursor.getLong(Projections.ID); - String lookupKey = cursor.getString(Projections.LOOKUP_KEY); - return Contacts.getLookupUri(contactId, lookupKey) - .buildUpon() - .appendQueryParameter( - ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(cursor.getDirectoryId())) - .build(); - } - - @Override - public void onClick(View v) { - PreCall.start(context, new CallIntentBuilder(number, CallInitiationType.Type.REGULAR_SEARCH)); - } -} diff --git a/java/com/android/dialer/searchfragment/remote/RemoteContactsCursor.java b/java/com/android/dialer/searchfragment/remote/RemoteContactsCursor.java deleted file mode 100644 index 653c67041..000000000 --- a/java/com/android/dialer/searchfragment/remote/RemoteContactsCursor.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * 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.contacts.common.compat.DirectoryCompat; -import com.android.dialer.common.Assert; -import com.android.dialer.searchfragment.common.SearchCursor; -import com.android.dialer.searchfragment.directories.DirectoriesCursorLoader; -import com.android.dialer.searchfragment.directories.DirectoriesCursorLoader.Directory; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * {@link MergeCursor} used for combining remote directory cursors into one cursor. - * - *

Usually a device with multiple Google accounts will have multiple remote directories returned - * by {@link DirectoriesCursorLoader}, each represented as a {@link Directory}. - * - *

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 { - - /** - * {@link SearchCursor#HEADER_PROJECTION} with {@link #COLUMN_DIRECTORY_ID} appended on the end. - * - *

This is needed to get the directoryId associated with each contact. directoryIds are needed - * to load the correct quick contact card. - */ - private static final String[] PROJECTION = buildProjection(); - - private static final String COLUMN_DIRECTORY_ID = "directory_id"; - - /** - * 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 directories) { - Assert.checkArgument( - cursors.length == directories.size(), - "Directories (%d) and cursors (%d) must be the same size.", - directories.size(), - cursors.length); - 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 directories) { - List 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(), directory.getId())); - cursorList.add(cursor); - } - return cursorList.toArray(new Cursor[cursorList.size()]); - } - - private static MatrixCursor createHeaderCursor(Context context, String name, long id) { - MatrixCursor headerCursor = new MatrixCursor(PROJECTION, 1); - if (DirectoryCompat.isOnlyEnterpriseDirectoryId(id)) { - headerCursor.addRow( - new Object[] {context.getString(R.string.directory_search_label_work), id}); - } else { - headerCursor.addRow(new Object[] {context.getString(R.string.directory, name), id}); - } - return headerCursor; - } - - private static String[] buildProjection() { - String[] projection = Arrays.copyOf(HEADER_PROJECTION, HEADER_PROJECTION.length + 1); - projection[projection.length - 1] = COLUMN_DIRECTORY_ID; - return projection; - } - - /** 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 long getDirectoryId() { - int position = getPosition(); - // proceed backwards until we reach the header row, which contains the directory ID. - while (moveToPrevious()) { - int columnIndex = getColumnIndex(COLUMN_DIRECTORY_ID); - if (columnIndex == -1) { - continue; - } - - int id = getInt(columnIndex); - if (id == -1) { - continue; - } - - // return the cursor to it's original position/state - moveToPosition(position); - return id; - } - throw Assert.createIllegalStateFailException("No directory id for contact at: " + position); - } - - @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 deleted file mode 100644 index cf495e49c..000000000 --- a/java/com/android/dialer/searchfragment/remote/RemoteContactsCursorLoader.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * 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.content.CursorLoader; -import android.database.Cursor; -import android.database.MatrixCursor; -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.NonNull; -import android.support.annotation.VisibleForTesting; -import com.android.contacts.common.compat.DirectoryCompat; -import com.android.dialer.searchfragment.common.Projections; -import com.android.dialer.searchfragment.directories.DirectoriesCursorLoader.Directory; -import java.util.ArrayList; -import java.util.List; - -/** - * Cursor loader to load extended contacts on device. - * - *

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"); - - private static final String IGNORE_NUMBER_TOO_LONG_CLAUSE = "length(" + Phone.NUMBER + ") < 1000"; - private static final String PHONE_NUMBER_NOT_NULL = Phone.NUMBER + " IS NOT NULL"; - private static final String MAX_RESULTS = "10"; - - private final String query; - private final List directories; - private final Cursor[] cursors; - - public RemoteContactsCursorLoader(Context context, String query, List directories) { - super( - context, - null, - Projections.DATA_PROJECTION, - IGNORE_NUMBER_TOO_LONG_CLAUSE + " AND " + PHONE_NUMBER_NOT_NULL, - null, - Phone.SORT_KEY_PRIMARY); - this.query = query; - this.directories = new ArrayList<>(directories); - cursors = new Cursor[directories.size()]; - } - - @Override - public Cursor loadInBackground() { - for (int i = 0; i < directories.size(); i++) { - Directory directory = directories.get(i); - - // Filter out local directories - if (!DirectoryCompat.isRemoteDirectoryId(directory.getId()) - && !DirectoryCompat.isEnterpriseDirectoryId(directory.getId())) { - cursors[i] = null; - continue; - } - - // Filter out invisible directories - if (DirectoryCompat.isInvisibleDirectory(directory.getId())) { - cursors[i] = null; - continue; - } - - Cursor cursor = - getContext() - .getContentResolver() - .query( - getContentFilterUri(query, directory.getId()), - getProjection(), - getSelection(), - getSelectionArgs(), - getSortOrder()); - // Even though the cursor specifies "WHERE PHONE_NUMBER IS NOT NULL" the Blackberry Hub app's - // directory extension doesn't appear to respect it, and sometimes returns a null phone - // number. In this case just hide the row entirely. See a bug. - cursors[i] = createMatrixCursorFilteringNullNumbers(cursor); - } - return RemoteContactsCursor.newInstance(getContext(), cursors, directories); - } - - private MatrixCursor createMatrixCursorFilteringNullNumbers(Cursor cursor) { - if (cursor == null) { - return null; - } - MatrixCursor matrixCursor = new MatrixCursor(cursor.getColumnNames()); - try { - if (cursor.moveToFirst()) { - do { - String number = cursor.getString(Projections.PHONE_NUMBER); - if (number == null) { - continue; - } - matrixCursor.addRow(objectArrayFromCursor(cursor)); - } while (cursor.moveToNext()); - } - } finally { - cursor.close(); - } - return matrixCursor; - } - - @NonNull - private static Object[] objectArrayFromCursor(@NonNull Cursor cursor) { - Object[] values = new Object[cursor.getColumnCount()]; - for (int i = 0; i < cursor.getColumnCount(); i++) { - int fieldType = cursor.getType(i); - if (fieldType == Cursor.FIELD_TYPE_BLOB) { - values[i] = cursor.getBlob(i); - } else if (fieldType == Cursor.FIELD_TYPE_FLOAT) { - values[i] = cursor.getDouble(i); - } else if (fieldType == Cursor.FIELD_TYPE_INTEGER) { - values[i] = cursor.getLong(i); - } else if (fieldType == Cursor.FIELD_TYPE_STRING) { - values[i] = cursor.getString(i); - } else if (fieldType == Cursor.FIELD_TYPE_NULL) { - values[i] = null; - } else { - throw new IllegalStateException("Unknown fieldType (" + fieldType + ") for column: " + i); - } - } - return values; - } - - @VisibleForTesting - static Uri getContentFilterUri(String query, long directoryId) { - Uri baseUri = - VERSION.SDK_INT >= VERSION_CODES.N - ? ENTERPRISE_CONTENT_FILTER_URI - : Phone.CONTENT_FILTER_URI; - - return baseUri - .buildUpon() - .appendPath(query) - .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(directoryId)) - .appendQueryParameter(ContactsContract.REMOVE_DUPLICATE_ENTRIES, "true") - .appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY, MAX_RESULTS) - .build(); - } -} diff --git a/java/com/android/dialer/searchfragment/remote/res/values/strings.xml b/java/com/android/dialer/searchfragment/remote/res/values/strings.xml deleted file mode 100644 index beabba135..000000000 --- a/java/com/android/dialer/searchfragment/remote/res/values/strings.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - Directory %1$s - \ No newline at end of file -- cgit v1.2.3