diff options
author | calderwoodra <calderwoodra@google.com> | 2017-10-18 04:49:12 +0000 |
---|---|---|
committer | android-build-merger <android-build-merger@google.com> | 2017-10-18 04:49:12 +0000 |
commit | df419c43b14e58e5241867e3bd2962a7b555f74f (patch) | |
tree | 7606a99d60659b38dbc4895e1826afac70e550de /java/com/android/dialer/searchfragment/cp2/ContactFilterCursor.java | |
parent | 7f1dd720b96504ce706e4d7d8867211048bf5cfa (diff) | |
parent | 4b46ec2258373c76c44c900a3cfe93c1d28935a8 (diff) |
Merge changes Ib92b055d,I5c9f66ff
am: 4b46ec2258
Change-Id: I8cbfb88558d46cbdd3ed696b56774913d1839a78
Diffstat (limited to 'java/com/android/dialer/searchfragment/cp2/ContactFilterCursor.java')
-rw-r--r-- | java/com/android/dialer/searchfragment/cp2/ContactFilterCursor.java | 209 |
1 files changed, 146 insertions, 63 deletions
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. * - * <p>The query can have at least 1 of 3 forms: + * <p>Here are some sample rows and columns that might exist in cp2 database: * * <ul> - * <li>A phone number - * <li>A T9 representation of a name (matches {@link QueryFilteringUtil#T9_PATTERN}). - * <li>A name + * <li>display Name (William), contactID (202), mimeType (name), data1 (A Pixel) + * <li>display Name (William), contactID (202), mimeType (phone), data1 (+1 650-200-3333) + * <li>display Name (William), contactID (202), mimeType (phone), data1 (+1 540-555-6666) + * <li>display Name (William), contactID (202), mimeType (organization), data1 (Walmart) + * <li>display Name (William), contactID (202), mimeType (nickname), data1 (Will) * </ul> * - * <p>A contact is considered a match if: + * <p>These rows would be coalesced into new rows like so: * * <ul> - * <li>Its phone number contains the phone number query - * <li>Its name represented in T9 contains the T9 query - * <li>Its name contains the query + * <li>display Name (William), phoneNumber (+1 650-200-3333), organization (Walmart), nickname + * (Will) + * <li>display Name (William), phoneNumber (+1 540-555-6666), organization (Walmart), nickname + * (Will) * </ul> */ - public void filter(@Nullable String query) { - if (query == null) { - query = ""; + private static Cursor createCursor(Cursor cursor) { + // Convert cursor rows into Cp2Contacts + List<Cp2Contact> cp2Contacts = new ArrayList<>(); + Set<Integer> 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<Cp2Contact> coalescedContacts = new ArrayList<>(); + for (Integer contactId : contactIds) { + List<Cp2Contact> 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<Cp2Contact> coalesceContacts(List<Cp2Contact> contactsWithSameContactId) { + String companyName = null; + String nickName = null; + List<Cp2Contact> 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<Cp2Contact> coalescedContacts = new ArrayList<>(); + for (Cp2Contact phoneContact : phoneContacts) { + coalescedContacts.add( + phoneContact.toBuilder().setCompanyName(companyName).setNickName(nickName).build()); + } + return coalescedContacts; + } + + private static void removeDuplicatePhoneNumbers(List<Cp2Contact> 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<Cp2Contact> getAllContactsWithContactId( + int contactId, List<Cp2Contact> contacts) { + List<Cp2Contact> 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. + * + * <p>The query can have at least 1 of 3 forms: + * + * <ul> + * <li>A phone number + * <li>A T9 representation of a name (matches {@link QueryFilteringUtil#T9_PATTERN}). + * <li>A name + * </ul> + * + * <p>A contact is considered a match if: + * + * <ul> + * <li>Its phone number contains the phone number query + * <li>Its name represented in T9 contains the T9 query + * <li>Its name contains the query + * <li>Its company contains the query + * </ul> + */ + 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; |