From fe7e9e1d5a083bfe376df0d54bcf632f60012dcf Mon Sep 17 00:00:00 2001 From: calderwoodra Date: Tue, 17 Oct 2017 15:54:45 -0700 Subject: Contacts are now searchable by company name. This change coalesces Cp2 contacts into a new cursor so that they can be associated with the Company name. The following logs can help explain how the data is organizes in the original cursor: display Name (A Pixel), lookupKey (3535i7a9673fc89b77de3), mimeType (vnd.android.cursor.item/name), data1 (A Pixel) display Name (A Pixel), lookupKey (3535i7a9673fc89b77de3), mimeType (vnd.android.cursor.item/note), data1 () display Name (A Pixel), lookupKey (3535i7a9673fc89b77de3), mimeType (vnd.android.cursor.item/group_membership), data1 (1) display Name (A Pixel), lookupKey (3535i7a9673fc89b77de3), mimeType (vnd.android.cursor.item/phone_v2), data1 (+1 650-200-7932) display Name (A Pixel), lookupKey (3535i7a9673fc89b77de3), mimeType (vnd.android.cursor.item/phone_v2), data1 (+1 540-555-6666) display Name (A Pixel), lookupKey (3535i7a9673fc89b77de3), mimeType (vnd.android.cursor.item/organization), data1 (Walmart) This is an example of what is returned for a single contact. We can easily associate contact rows together using the lookup key and determine which rows have relevant data by checking the mime type. I use the data here to coalesce the contacts together into one row for easy parsing in ContactFilterCursor. Rows with mime type phone_v2 contain contact information (for example, this contact has 2 phone numbers). Rows with mime type organization contain contact's company information (for example, this contact works at Walmart). Bug: 67675742,64894607,67848713 Test: existing + SCCT.filter_companyName PiperOrigin-RevId: 172528797 Change-Id: I5c9f66ff0c27276869295eff97bb0216f92995be --- .../dialer/searchfragment/common/Projections.java | 44 +++-- .../searchfragment/common/QueryFilteringUtil.java | 4 + .../searchfragment/cp2/ContactFilterCursor.java | 209 ++++++++++++++------- .../dialer/searchfragment/cp2/Cp2Contact.java | 132 +++++++++++++ .../cp2/SearchContactViewHolder.java | 25 ++- .../cp2/SearchContactsCursorLoader.java | 22 ++- .../nearbyplaces/NearbyPlaceViewHolder.java | 8 +- .../nearbyplaces/NearbyPlacesCursorLoader.java | 2 +- .../remote/RemoteContactViewHolder.java | 14 +- .../remote/RemoteContactsCursorLoader.java | 2 +- .../searchfragment/testing/TestCursorSchema.java | 47 +++++ 11 files changed, 400 insertions(+), 109 deletions(-) create mode 100644 java/com/android/dialer/searchfragment/cp2/Cp2Contact.java create mode 100644 java/com/android/dialer/searchfragment/testing/TestCursorSchema.java diff --git a/java/com/android/dialer/searchfragment/common/Projections.java b/java/com/android/dialer/searchfragment/common/Projections.java index 078c3e5e6..aaf9e80f1 100644 --- a/java/com/android/dialer/searchfragment/common/Projections.java +++ b/java/com/android/dialer/searchfragment/common/Projections.java @@ -16,37 +16,47 @@ package com.android.dialer.searchfragment.common; +import android.provider.ContactsContract.CommonDataKinds.Nickname; +import android.provider.ContactsContract.CommonDataKinds.Organization; import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.provider.ContactsContract.Data; /** Class containing relevant projections for searching contacts. */ public class Projections { - public static final int PHONE_ID = 0; + public static final int ID = 0; public static final int PHONE_TYPE = 1; public static final int PHONE_LABEL = 2; public static final int PHONE_NUMBER = 3; - public static final int PHONE_DISPLAY_NAME = 4; - public static final int PHONE_PHOTO_ID = 5; - public static final int PHONE_PHOTO_URI = 6; - public static final int PHONE_LOOKUP_KEY = 7; - public static final int PHONE_CARRIER_PRESENCE = 8; - public static final int PHONE_CONTACT_ID = 9; + public static final int DISPLAY_NAME = 4; + public static final int PHOTO_ID = 5; + public static final int PHOTO_URI = 6; + public static final int LOOKUP_KEY = 7; + public static final int CARRIER_PRESENCE = 8; + public static final int CONTACT_ID = 9; + public static final int MIME_TYPE = 10; @SuppressWarnings("unused") - public static final int PHONE_SORT_KEY = 10; + public static final int SORT_KEY = 11; - public static final String[] PHONE_PROJECTION = + public static final int COMPANY_NAME = 12; + public static final int NICKNAME = 13; + + public static final String[] DATA_PROJECTION = new String[] { - Phone._ID, // 0 + Data._ID, // 0 Phone.TYPE, // 1 Phone.LABEL, // 2 Phone.NUMBER, // 3 - Phone.DISPLAY_NAME_PRIMARY, // 4 - Phone.PHOTO_ID, // 5 - Phone.PHOTO_THUMBNAIL_URI, // 6 - Phone.LOOKUP_KEY, // 7 - Phone.CARRIER_PRESENCE, // 8 - Phone.CONTACT_ID, // 9 - Phone.SORT_KEY_PRIMARY // 10 + Data.DISPLAY_NAME_PRIMARY, // 4 + Data.PHOTO_ID, // 5 + Data.PHOTO_THUMBNAIL_URI, // 6 + Data.LOOKUP_KEY, // 7 + Data.CARRIER_PRESENCE, // 8 + Data.CONTACT_ID, // 9 + Data.MIMETYPE, // 10 + Data.SORT_KEY_PRIMARY, // 11 + Organization.COMPANY, // 12 + Nickname.NAME // 13 }; } diff --git a/java/com/android/dialer/searchfragment/common/QueryFilteringUtil.java b/java/com/android/dialer/searchfragment/common/QueryFilteringUtil.java index 775f8deec..6b5cea88d 100644 --- a/java/com/android/dialer/searchfragment/common/QueryFilteringUtil.java +++ b/java/com/android/dialer/searchfragment/common/QueryFilteringUtil.java @@ -81,6 +81,10 @@ public class QueryFilteringUtil { * */ public static boolean nameContainsQuery(String query, String name) { + if (TextUtils.isEmpty(name)) { + return false; + } + return Pattern.compile("(^|\\s)" + Pattern.quote(query.toLowerCase())) .matcher(name.toLowerCase()) .find(); diff --git a/java/com/android/dialer/searchfragment/cp2/ContactFilterCursor.java b/java/com/android/dialer/searchfragment/cp2/ContactFilterCursor.java index 6fd053cae..9a0ca0088 100644 --- a/java/com/android/dialer/searchfragment/cp2/ContactFilterCursor.java +++ b/java/com/android/dialer/searchfragment/cp2/ContactFilterCursor.java @@ -21,17 +21,25 @@ import android.database.CharArrayBuffer; import android.database.ContentObserver; import android.database.Cursor; import android.database.DataSetObserver; +import android.database.MatrixCursor; import android.net.Uri; import android.os.Bundle; +import android.provider.ContactsContract.CommonDataKinds.Nickname; +import android.provider.ContactsContract.CommonDataKinds.Organization; +import android.provider.ContactsContract.CommonDataKinds.Phone; import android.support.annotation.IntDef; import android.support.annotation.Nullable; +import android.support.v4.util.ArraySet; import android.text.TextUtils; +import com.android.dialer.common.Assert; import com.android.dialer.searchfragment.common.Projections; import com.android.dialer.searchfragment.common.QueryFilteringUtil; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Set; /** * Wrapper for a cursor containing all on device contacts. @@ -63,92 +71,109 @@ final class ContactFilterCursor implements Cursor { } /** - * @param cursor with projection {@link Projections#PHONE_PROJECTION}. + * @param cursor with projection {@link Projections#DATA_PROJECTION}. * @param query to filter cursor results. */ ContactFilterCursor(Cursor cursor, @Nullable String query) { - // TODO(calderwoodra) investigate copying this into a MatrixCursor and holding in memory - this.cursor = cursor; + this.cursor = createCursor(cursor); filter(query); } /** - * Filters out contacts that do not match the query. + * Returns a new cursor with contact information coalesced. * - *

The query can have at least 1 of 3 forms: + *

Here are some sample rows and columns that might exist in cp2 database: * *

* - *

A contact is considered a match if: + *

These rows would be coalesced into new rows like so: * *

*/ - public void filter(@Nullable String query) { - if (query == null) { - query = ""; + private static Cursor createCursor(Cursor cursor) { + // Convert cursor rows into Cp2Contacts + List cp2Contacts = new ArrayList<>(); + Set contactIds = new ArraySet<>(); + cursor.moveToPosition(-1); + while (cursor.moveToNext()) { + Cp2Contact contact = Cp2Contact.fromCursor(cursor); + cp2Contacts.add(contact); + contactIds.add(contact.contactId()); } - queryFilteredPositions.clear(); + cursor.close(); - // On some devices, contacts have multiple rows with identical phone numbers. These numbers are - // considered duplicates. Since the order might not be guaranteed, we compare all of the numbers - // and hold onto the most qualified one as the one we want to display to the user. - // See #getQualification for details on how qualification is determined. - int previousMostQualifiedPosition = 0; - String previousName = ""; - String previousMostQualifiedNumber = ""; + // Group then combine contact data + List coalescedContacts = new ArrayList<>(); + for (Integer contactId : contactIds) { + List duplicateContacts = getAllContactsWithContactId(contactId, cp2Contacts); + coalescedContacts.addAll(coalesceContacts(duplicateContacts)); + } - query = query.toLowerCase(); - cursor.moveToPosition(-1); + // Sort by display name, then build new cursor from coalesced contacts. + // We sort the contacts so that they are displayed to the user in lexicographic order. + Collections.sort(coalescedContacts, (o1, o2) -> o1.displayName().compareTo(o2.displayName())); + MatrixCursor newCursor = + new MatrixCursor(Projections.DATA_PROJECTION, coalescedContacts.size()); + for (Cp2Contact contact : coalescedContacts) { + newCursor.addRow(contact.toCursorRow()); + } + return newCursor; + } + + private static List coalesceContacts(List contactsWithSameContactId) { + String companyName = null; + String nickName = null; + List phoneContacts = new ArrayList<>(); + for (Cp2Contact contact : contactsWithSameContactId) { + if (contact.mimeType().equals(Phone.CONTENT_ITEM_TYPE)) { + phoneContacts.add(contact); + } else if (contact.mimeType().equals(Organization.CONTENT_ITEM_TYPE)) { + Assert.checkArgument(TextUtils.isEmpty(companyName)); + companyName = contact.companyName(); + } else if (contact.mimeType().equals(Nickname.CONTENT_ITEM_TYPE)) { + Assert.checkArgument(TextUtils.isEmpty(nickName)); + nickName = contact.nickName(); + } + } - while (cursor.moveToNext()) { - int position = cursor.getPosition(); - String currentNumber = cursor.getString(Projections.PHONE_NUMBER); - String currentName = cursor.getString(Projections.PHONE_DISPLAY_NAME); + removeDuplicatePhoneNumbers(phoneContacts); - if (!previousName.equals(currentName)) { - previousName = currentName; - previousMostQualifiedNumber = currentNumber; - previousMostQualifiedPosition = position; - } else { - // Since the contact name is the same, check if this number is a duplicate - switch (getQualification(currentNumber, previousMostQualifiedNumber)) { - case Qualification.CURRENT_MORE_QUALIFIED: - // Number is a less qualified duplicate, ignore it. - continue; - case Qualification.NEW_NUMBER_IS_MORE_QUALIFIED: - // If number wasn't filtered out before, remove it and add it's more qualified version. - int index = queryFilteredPositions.indexOf(previousMostQualifiedPosition); - if (index != -1) { - queryFilteredPositions.remove(index); - queryFilteredPositions.add(position); - } - previousMostQualifiedNumber = currentNumber; - previousMostQualifiedPosition = position; - continue; - case Qualification.NUMBERS_ARE_NOT_DUPLICATES: - default: - previousMostQualifiedNumber = currentNumber; - previousMostQualifiedPosition = position; + List coalescedContacts = new ArrayList<>(); + for (Cp2Contact phoneContact : phoneContacts) { + coalescedContacts.add( + phoneContact.toBuilder().setCompanyName(companyName).setNickName(nickName).build()); + } + return coalescedContacts; + } + + private static void removeDuplicatePhoneNumbers(List phoneContacts) { + for (int i = 0; i < phoneContacts.size(); i++) { + Cp2Contact contact1 = phoneContacts.get(i); + for (int j = i + 1; j < phoneContacts.size(); /* don't iterate by default */ ) { + Cp2Contact contact2 = phoneContacts.get(j); + int qualification = getQualification(contact2.phoneNumber(), contact1.phoneNumber()); + if (qualification == Qualification.CURRENT_MORE_QUALIFIED) { + phoneContacts.remove(contact2); + } else if (qualification == Qualification.NEW_NUMBER_IS_MORE_QUALIFIED) { + phoneContacts.remove(contact1); + break; + } else if (qualification == Qualification.NUMBERS_ARE_NOT_DUPLICATES) { + // Keep both contacts + j++; } } - - if (TextUtils.isEmpty(query) - || QueryFilteringUtil.nameMatchesT9Query(query, previousName) - || QueryFilteringUtil.numberMatchesNumberQuery(query, previousMostQualifiedNumber) - || QueryFilteringUtil.nameContainsQuery(query, previousName)) { - queryFilteredPositions.add(previousMostQualifiedPosition); - } } - currentPosition = 0; - cursor.moveToFirst(); } /** @@ -157,7 +182,7 @@ final class ContactFilterCursor implements Cursor { * @return {@link Qualification} where the more qualified number is the number with the most * digits. If the digits are the same, the number with the most formatting is more qualified. */ - private @Qualification int getQualification(String number, String mostQualifiedNumber) { + private static @Qualification int getQualification(String number, String mostQualifiedNumber) { // Ignore formatting String numberDigits = QueryFilteringUtil.digitsOnly(number); String qualifiedNumberDigits = QueryFilteringUtil.digitsOnly(mostQualifiedNumber); @@ -182,6 +207,64 @@ final class ContactFilterCursor implements Cursor { return Qualification.NUMBERS_ARE_NOT_DUPLICATES; } + private static List getAllContactsWithContactId( + int contactId, List contacts) { + List contactIdContacts = new ArrayList<>(); + for (Cp2Contact contact : contacts) { + if (contact.contactId() == contactId) { + contactIdContacts.add(contact); + } + } + return contactIdContacts; + } + + /** + * Filters out contacts that do not match the query. + * + *

The query can have at least 1 of 3 forms: + * + *

    + *
  • A phone number + *
  • A T9 representation of a name (matches {@link QueryFilteringUtil#T9_PATTERN}). + *
  • A name + *
+ * + *

A contact is considered a match if: + * + *

    + *
  • Its phone number contains the phone number query + *
  • Its name represented in T9 contains the T9 query + *
  • Its name contains the query + *
  • Its company contains the query + *
+ */ + public void filter(@Nullable String query) { + if (query == null) { + query = ""; + } + queryFilteredPositions.clear(); + query = query.toLowerCase(); + cursor.moveToPosition(-1); + + while (cursor.moveToNext()) { + int position = cursor.getPosition(); + String number = cursor.getString(Projections.PHONE_NUMBER); + String name = cursor.getString(Projections.DISPLAY_NAME); + String companyName = cursor.getString(Projections.COMPANY_NAME); + String nickName = cursor.getString(Projections.NICKNAME); + if (TextUtils.isEmpty(query) + || QueryFilteringUtil.nameMatchesT9Query(query, name) + || QueryFilteringUtil.numberMatchesNumberQuery(query, number) + || QueryFilteringUtil.nameContainsQuery(query, name) + || QueryFilteringUtil.nameContainsQuery(query, companyName) + || QueryFilteringUtil.nameContainsQuery(query, nickName)) { + queryFilteredPositions.add(position); + } + } + currentPosition = 0; + cursor.moveToFirst(); + } + @Override public boolean moveToPosition(int position) { currentPosition = position; diff --git a/java/com/android/dialer/searchfragment/cp2/Cp2Contact.java b/java/com/android/dialer/searchfragment/cp2/Cp2Contact.java new file mode 100644 index 000000000..f199f679b --- /dev/null +++ b/java/com/android/dialer/searchfragment/cp2/Cp2Contact.java @@ -0,0 +1,132 @@ +/* + * 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.cp2; + +import android.database.Cursor; +import android.support.annotation.Nullable; +import com.android.dialer.searchfragment.common.Projections; +import com.google.auto.value.AutoValue; + +/** POJO Representation for contacts returned in {@link SearchContactsCursorLoader}. */ +@AutoValue +public abstract class Cp2Contact { + + public abstract long phoneId(); + + public abstract int phoneType(); + + @Nullable + public abstract String phoneLabel(); + + public abstract String phoneNumber(); + + @Nullable + public abstract String displayName(); + + public abstract int photoId(); + + @Nullable + public abstract String photoUri(); + + public abstract String lookupKey(); + + public abstract int carrierPresence(); + + public abstract int contactId(); + + @Nullable + public abstract String companyName(); + + @Nullable + public abstract String nickName(); + + public abstract String mimeType(); + + /** Builder for {@link Cp2Contact}. */ + @AutoValue.Builder + public abstract static class Builder { + public abstract Builder setPhoneId(long id); + + public abstract Builder setPhoneType(int type); + + public abstract Builder setPhoneLabel(@Nullable String label); + + public abstract Builder setPhoneNumber(String number); + + public abstract Builder setDisplayName(@Nullable String name); + + public abstract Builder setPhotoId(int id); + + public abstract Builder setPhotoUri(@Nullable String uri); + + public abstract Builder setLookupKey(String lookupKey); + + public abstract Builder setCarrierPresence(int presence); + + public abstract Builder setContactId(int id); + + public abstract Builder setCompanyName(@Nullable String name); + + public abstract Builder setNickName(@Nullable String nickName); + + public abstract Builder setMimeType(String mimeType); + + public abstract Cp2Contact build(); + } + + public static Builder builder() { + return new AutoValue_Cp2Contact.Builder(); + } + + public static Cp2Contact fromCursor(Cursor cursor) { + return Cp2Contact.builder() + .setPhoneId(cursor.getLong(Projections.CONTACT_ID)) + .setPhoneType(cursor.getInt(Projections.PHONE_TYPE)) + .setPhoneLabel(cursor.getString(Projections.PHONE_LABEL)) + .setPhoneNumber(cursor.getString(Projections.PHONE_NUMBER)) + .setDisplayName(cursor.getString(Projections.DISPLAY_NAME)) + .setPhotoId(cursor.getInt(Projections.PHOTO_ID)) + .setPhotoUri(cursor.getString(Projections.PHOTO_URI)) + .setLookupKey(cursor.getString(Projections.LOOKUP_KEY)) + .setCarrierPresence(cursor.getInt(Projections.CARRIER_PRESENCE)) + .setContactId(cursor.getInt(Projections.CONTACT_ID)) + .setCompanyName(cursor.getString(Projections.COMPANY_NAME)) + .setNickName(cursor.getString(Projections.NICKNAME)) + .setMimeType(cursor.getString(Projections.MIME_TYPE)) + .build(); + } + + public Object[] toCursorRow() { + Object[] row = new Object[Projections.DATA_PROJECTION.length]; + row[Projections.ID] = phoneId(); + row[Projections.PHONE_TYPE] = phoneType(); + row[Projections.PHONE_LABEL] = phoneLabel(); + row[Projections.PHONE_NUMBER] = phoneNumber(); + row[Projections.DISPLAY_NAME] = displayName(); + row[Projections.PHOTO_ID] = photoId(); + row[Projections.PHOTO_URI] = photoUri(); + row[Projections.LOOKUP_KEY] = lookupKey(); + row[Projections.CARRIER_PRESENCE] = carrierPresence(); + row[Projections.CONTACT_ID] = contactId(); + row[Projections.COMPANY_NAME] = companyName(); + row[Projections.NICKNAME] = nickName(); + row[Projections.MIME_TYPE] = mimeType(); + return row; + } + + public abstract Builder toBuilder(); +} diff --git a/java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java b/java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java index b162a5e52..c09396c72 100644 --- a/java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java +++ b/java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java @@ -96,7 +96,7 @@ public final class SearchContactViewHolder extends ViewHolder implements OnClick dialerContact = getDialerContact(context, cursor); position = cursor.getPosition(); number = cursor.getString(Projections.PHONE_NUMBER); - String name = cursor.getString(Projections.PHONE_DISPLAY_NAME); + String name = cursor.getString(Projections.DISPLAY_NAME); String label = getLabel(context.getResources(), cursor); String secondaryInfo = TextUtils.isEmpty(label) @@ -111,12 +111,12 @@ public final class SearchContactViewHolder extends ViewHolder implements OnClick if (shouldShowPhoto(cursor)) { nameOrNumberView.setVisibility(View.VISIBLE); photo.setVisibility(View.VISIBLE); - String photoUri = cursor.getString(Projections.PHONE_PHOTO_URI); + String photoUri = cursor.getString(Projections.PHOTO_URI); ContactPhotoManager.getInstance(context) .loadDialerThumbnailOrPhoto( photo, getContactUri(cursor), - cursor.getLong(Projections.PHONE_PHOTO_ID), + cursor.getLong(Projections.PHOTO_ID), photoUri == null ? null : Uri.parse(photoUri), name, LetterTileDrawable.TYPE_DEFAULT); @@ -129,11 +129,11 @@ 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); + String currentLookupKey = cursor.getString(Projections.LOOKUP_KEY); cursor.moveToPosition(currentPosition - 1); if (!cursor.isHeader() && !cursor.isBeforeFirst()) { - String previousLookupKey = cursor.getString(Projections.PHONE_LOOKUP_KEY); + String previousLookupKey = cursor.getString(Projections.LOOKUP_KEY); cursor.moveToPosition(currentPosition); return !currentLookupKey.equals(previousLookupKey); } @@ -142,8 +142,8 @@ public final class SearchContactViewHolder extends ViewHolder implements OnClick } private static Uri getContactUri(Cursor cursor) { - long contactId = cursor.getLong(Projections.PHONE_ID); - String lookupKey = cursor.getString(Projections.PHONE_LOOKUP_KEY); + long contactId = cursor.getLong(Projections.ID); + String lookupKey = cursor.getString(Projections.LOOKUP_KEY); return Contacts.getLookupUri(contactId, lookupKey); } @@ -188,7 +188,7 @@ public final class SearchContactViewHolder extends ViewHolder implements OnClick private static @CallToAction int getCallToAction( Context context, SearchCursor cursor, String query) { - int carrierPresence = cursor.getInt(Projections.PHONE_CARRIER_PRESENCE); + int carrierPresence = cursor.getInt(Projections.CARRIER_PRESENCE); String number = cursor.getString(Projections.PHONE_NUMBER); if ((carrierPresence & Phone.CARRIER_PRESENCE_VT_CAPABLE) == 1) { return CallToAction.VIDEO_CALL; @@ -262,16 +262,15 @@ public final class SearchContactViewHolder extends ViewHolder implements OnClick private static DialerContact getDialerContact(Context context, Cursor cursor) { DialerContact.Builder contact = DialerContact.newBuilder(); - String displayName = cursor.getString(Projections.PHONE_DISPLAY_NAME); + String displayName = cursor.getString(Projections.DISPLAY_NAME); String number = cursor.getString(Projections.PHONE_NUMBER); Uri contactUri = Contacts.getLookupUri( - cursor.getLong(Projections.PHONE_CONTACT_ID), - cursor.getString(Projections.PHONE_LOOKUP_KEY)); + cursor.getLong(Projections.CONTACT_ID), cursor.getString(Projections.LOOKUP_KEY)); contact .setNumber(number) - .setPhotoId(cursor.getLong(Projections.PHONE_PHOTO_ID)) + .setPhotoId(cursor.getLong(Projections.PHOTO_ID)) .setContactType(LetterTileDrawable.TYPE_DEFAULT) .setNameOrNumber(displayName) .setNumberLabel( @@ -281,7 +280,7 @@ public final class SearchContactViewHolder extends ViewHolder implements OnClick cursor.getString(Projections.PHONE_LABEL)) .toString()); - String photoUri = cursor.getString(Projections.PHONE_PHOTO_URI); + String photoUri = cursor.getString(Projections.PHOTO_URI); if (photoUri != null) { contact.setPhotoUri(photoUri); } diff --git a/java/com/android/dialer/searchfragment/cp2/SearchContactsCursorLoader.java b/java/com/android/dialer/searchfragment/cp2/SearchContactsCursorLoader.java index b7fc9b5c5..f1230c6d9 100644 --- a/java/com/android/dialer/searchfragment/cp2/SearchContactsCursorLoader.java +++ b/java/com/android/dialer/searchfragment/cp2/SearchContactsCursorLoader.java @@ -19,7 +19,10 @@ package com.android.dialer.searchfragment.cp2; import android.content.Context; import android.content.CursorLoader; import android.database.Cursor; +import android.provider.ContactsContract.CommonDataKinds.Nickname; +import android.provider.ContactsContract.CommonDataKinds.Organization; import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.provider.ContactsContract.Data; import android.support.annotation.Nullable; import com.android.dialer.searchfragment.common.Projections; @@ -32,14 +35,27 @@ public final class SearchContactsCursorLoader extends CursorLoader { public SearchContactsCursorLoader(Context context, @Nullable String query) { super( context, - Phone.CONTENT_URI, - Projections.PHONE_PROJECTION, - null, + Data.CONTENT_URI, + Projections.DATA_PROJECTION, + whereStatement(), null, Phone.SORT_KEY_PRIMARY + " ASC"); this.query = query; } + private static String whereStatement() { + return (Phone.NUMBER + " IS NOT NULL") + + " AND " + + Data.MIMETYPE + + " IN (\'" + + Phone.CONTENT_ITEM_TYPE + + "\', \'" + + Nickname.CONTENT_ITEM_TYPE + + "\', \'" + + Organization.CONTENT_ITEM_TYPE + + "\')"; + } + @Override public Cursor loadInBackground() { // All contacts diff --git a/java/com/android/dialer/searchfragment/nearbyplaces/NearbyPlaceViewHolder.java b/java/com/android/dialer/searchfragment/nearbyplaces/NearbyPlaceViewHolder.java index fa0782623..5d5188059 100644 --- a/java/com/android/dialer/searchfragment/nearbyplaces/NearbyPlaceViewHolder.java +++ b/java/com/android/dialer/searchfragment/nearbyplaces/NearbyPlaceViewHolder.java @@ -61,17 +61,17 @@ public final class NearbyPlaceViewHolder extends RecyclerView.ViewHolder */ public void bind(SearchCursor cursor, String query) { number = cursor.getString(Projections.PHONE_NUMBER); - String name = cursor.getString(Projections.PHONE_DISPLAY_NAME); + String name = cursor.getString(Projections.DISPLAY_NAME); String address = cursor.getString(Projections.PHONE_LABEL); placeName.setText(QueryBoldingUtil.getNameWithQueryBolded(query, name)); placeAddress.setText(QueryBoldingUtil.getNameWithQueryBolded(query, address)); - String photoUri = cursor.getString(Projections.PHONE_PHOTO_URI); + String photoUri = cursor.getString(Projections.PHOTO_URI); ContactPhotoManager.getInstance(context) .loadDialerThumbnailOrPhoto( photo, getContactUri(cursor), - cursor.getLong(Projections.PHONE_PHOTO_ID), + cursor.getLong(Projections.PHOTO_ID), photoUri == null ? null : Uri.parse(photoUri), name, LetterTileDrawable.TYPE_BUSINESS); @@ -81,7 +81,7 @@ public final class NearbyPlaceViewHolder extends RecyclerView.ViewHolder // Since the lookup key for Nearby Places is actually a JSON representation of the information, // we need to pass it in as an encoded fragment in our contact uri. // It includes information like display name, photo uri, phone number, ect. - String businessInfoJson = cursor.getString(Projections.PHONE_LOOKUP_KEY); + String businessInfoJson = cursor.getString(Projections.LOOKUP_KEY); return Contacts.CONTENT_LOOKUP_URI .buildUpon() .appendPath(Constants.LOOKUP_URI_ENCODED) diff --git a/java/com/android/dialer/searchfragment/nearbyplaces/NearbyPlacesCursorLoader.java b/java/com/android/dialer/searchfragment/nearbyplaces/NearbyPlacesCursorLoader.java index c8bb36a73..0d52c108e 100644 --- a/java/com/android/dialer/searchfragment/nearbyplaces/NearbyPlacesCursorLoader.java +++ b/java/com/android/dialer/searchfragment/nearbyplaces/NearbyPlacesCursorLoader.java @@ -41,7 +41,7 @@ public final class NearbyPlacesCursorLoader extends CursorLoader { */ public NearbyPlacesCursorLoader( Context context, String query, @NonNull List directoryIds) { - super(context, getContentUri(context, query), Projections.PHONE_PROJECTION, null, null, null); + super(context, getContentUri(context, query), Projections.DATA_PROJECTION, null, null, null); this.directoryId = getDirectoryId(directoryIds); } diff --git a/java/com/android/dialer/searchfragment/remote/RemoteContactViewHolder.java b/java/com/android/dialer/searchfragment/remote/RemoteContactViewHolder.java index df3eacc5b..8a02eb9b9 100644 --- a/java/com/android/dialer/searchfragment/remote/RemoteContactViewHolder.java +++ b/java/com/android/dialer/searchfragment/remote/RemoteContactViewHolder.java @@ -64,7 +64,7 @@ public final class RemoteContactViewHolder extends RecyclerView.ViewHolder */ public void bind(SearchCursor cursor, String query) { number = cursor.getString(Projections.PHONE_NUMBER); - String name = cursor.getString(Projections.PHONE_DISPLAY_NAME); + String name = cursor.getString(Projections.DISPLAY_NAME); String label = getLabel(context.getResources(), cursor); String secondaryInfo = TextUtils.isEmpty(label) @@ -78,12 +78,12 @@ public final class RemoteContactViewHolder extends RecyclerView.ViewHolder if (shouldShowPhoto(cursor)) { nameView.setVisibility(View.VISIBLE); photo.setVisibility(View.VISIBLE); - String photoUri = cursor.getString(Projections.PHONE_PHOTO_URI); + String photoUri = cursor.getString(Projections.PHOTO_URI); ContactPhotoManager.getInstance(context) .loadDialerThumbnailOrPhoto( photo, getContactUri(cursor), - cursor.getLong(Projections.PHONE_PHOTO_ID), + cursor.getLong(Projections.PHOTO_ID), photoUri == null ? null : Uri.parse(photoUri), name, LetterTileDrawable.TYPE_DEFAULT); @@ -96,11 +96,11 @@ public final class RemoteContactViewHolder extends RecyclerView.ViewHolder // 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); + String currentLookupKey = cursor.getString(Projections.LOOKUP_KEY); cursor.moveToPosition(currentPosition - 1); if (!cursor.isHeader() && !cursor.isBeforeFirst()) { - String previousLookupKey = cursor.getString(Projections.PHONE_LOOKUP_KEY); + String previousLookupKey = cursor.getString(Projections.LOOKUP_KEY); cursor.moveToPosition(currentPosition); return !currentLookupKey.equals(previousLookupKey); } @@ -121,8 +121,8 @@ public final class RemoteContactViewHolder extends RecyclerView.ViewHolder } private static Uri getContactUri(SearchCursor cursor) { - long contactId = cursor.getLong(Projections.PHONE_ID); - String lookupKey = cursor.getString(Projections.PHONE_LOOKUP_KEY); + long contactId = cursor.getLong(Projections.ID); + String lookupKey = cursor.getString(Projections.LOOKUP_KEY); return Contacts.getLookupUri(contactId, lookupKey) .buildUpon() .appendQueryParameter( diff --git a/java/com/android/dialer/searchfragment/remote/RemoteContactsCursorLoader.java b/java/com/android/dialer/searchfragment/remote/RemoteContactsCursorLoader.java index 37695be50..eb472732f 100644 --- a/java/com/android/dialer/searchfragment/remote/RemoteContactsCursorLoader.java +++ b/java/com/android/dialer/searchfragment/remote/RemoteContactsCursorLoader.java @@ -54,7 +54,7 @@ public final class RemoteContactsCursorLoader extends CursorLoader { super( context, null, - Projections.PHONE_PROJECTION, + Projections.DATA_PROJECTION, IGNORE_NUMBER_TOO_LONG_CLAUSE, null, Phone.SORT_KEY_PRIMARY); diff --git a/java/com/android/dialer/searchfragment/testing/TestCursorSchema.java b/java/com/android/dialer/searchfragment/testing/TestCursorSchema.java new file mode 100644 index 000000000..375fdb50c --- /dev/null +++ b/java/com/android/dialer/searchfragment/testing/TestCursorSchema.java @@ -0,0 +1,47 @@ +/* + * 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.testing; + +import android.provider.ContactsContract.Data; + +/** Testing class containing cp2 cursor testing utilities. */ +public final class TestCursorSchema { + + /** + * If new rows are added to {@link + * com.android.dialer.searchfragment.common.Projections#DATA_PROJECTION}, this schema should be + * updated. + */ + // TODO(67909522): remove these extra columns and remove all references to "Phone." + public static final String[] SCHEMA = + new String[] { + Data._ID, // 0 + "data2", // 1 Phone Type + "data3", // 2 Phone Label + "data1", // 3 Phone Number, Organization Company + Data.DISPLAY_NAME_PRIMARY, // 4 + Data.PHOTO_ID, // 5 + Data.PHOTO_THUMBNAIL_URI, // 6 + Data.LOOKUP_KEY, // 7 + Data.CARRIER_PRESENCE, // 8 + Data.CONTACT_ID, // 9 + Data.MIMETYPE, // 10 + Data.SORT_KEY_PRIMARY, // 11 + "company_name", // 12, no constant because Organization.COMPANY.equals("data1") + "nick_name" // 13, no constant because Nickname.NAME.equals("data1") + }; +} -- cgit v1.2.3