From 82670fee34bfc922905f31d3904406eb0677b162 Mon Sep 17 00:00:00 2001 From: linyuh Date: Thu, 2 Nov 2017 18:05:33 -0700 Subject: Support dual alphabets in smart search when a secondary alphabet is available. Bug: 30215380 Test: QueryBoldingUtilTest, QueryFilteringUtilTest, ContactFilterCursorTest PiperOrigin-RevId: 174408771 Change-Id: I4c601b16dd90db6b7b2a05c9daa6804749ea2a43 --- .../searchfragment/common/QueryBoldingUtil.java | 17 ++-- .../searchfragment/common/QueryFilteringUtil.java | 101 ++++++++++++--------- .../searchfragment/cp2/ContactFilterCursor.java | 12 ++- .../cp2/SearchContactViewHolder.java | 2 +- .../searchfragment/cp2/SearchContactsCursor.java | 10 +- .../cp2/SearchContactsCursorLoader.java | 2 +- .../nearbyplaces/NearbyPlaceViewHolder.java | 4 +- .../remote/RemoteContactViewHolder.java | 4 +- 8 files changed, 86 insertions(+), 66 deletions(-) (limited to 'java/com/android/dialer/searchfragment') diff --git a/java/com/android/dialer/searchfragment/common/QueryBoldingUtil.java b/java/com/android/dialer/searchfragment/common/QueryBoldingUtil.java index 4413252f4..9ac6e7c5e 100644 --- a/java/com/android/dialer/searchfragment/common/QueryBoldingUtil.java +++ b/java/com/android/dialer/searchfragment/common/QueryBoldingUtil.java @@ -16,6 +16,7 @@ package com.android.dialer.searchfragment.common; +import android.content.Context; import android.graphics.Typeface; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -49,14 +50,16 @@ public class QueryBoldingUtil { * * @param query containing any characters * @param name of a contact/string that query will compare to + * @param context of the app * @return name with query bolded if query can be found in the name. */ - public static CharSequence getNameWithQueryBolded(@Nullable String query, @NonNull String name) { + public static CharSequence getNameWithQueryBolded( + @Nullable String query, @NonNull String name, @NonNull Context context) { if (TextUtils.isEmpty(query)) { return name; } - if (!QueryFilteringUtil.nameMatchesT9Query(query, name)) { + if (!QueryFilteringUtil.nameMatchesT9Query(query, name, context)) { Pattern pattern = Pattern.compile("(^|\\s)" + Pattern.quote(query.toLowerCase())); Matcher matcher = pattern.matcher(name.toLowerCase()); if (matcher.find()) { @@ -69,7 +72,7 @@ public class QueryBoldingUtil { } Pattern pattern = Pattern.compile("(^|\\s)" + Pattern.quote(query.toLowerCase())); - Matcher matcher = pattern.matcher(QueryFilteringUtil.getT9Representation(name)); + Matcher matcher = pattern.matcher(QueryFilteringUtil.getT9Representation(name, context)); if (matcher.find()) { // query matches the start of a T9 name (i.e. 75 -> "Jessica [Jo]nes") int index = matcher.start(); @@ -79,11 +82,12 @@ public class QueryBoldingUtil { } else { // query match the T9 initials (i.e. 222 -> "[A]l [B]ob [C]harlie") - return getNameWithInitialsBolded(query, name); + return getNameWithInitialsBolded(query, name, context); } } - private static CharSequence getNameWithInitialsBolded(String query, String name) { + private static CharSequence getNameWithInitialsBolded( + String query, String name, Context context) { SpannableString boldedInitials = new SpannableString(name); name = name.toLowerCase(); int initialsBolded = 0; @@ -91,7 +95,8 @@ public class QueryBoldingUtil { while (++nameIndex < name.length() && initialsBolded < query.length()) { if ((nameIndex == 0 || name.charAt(nameIndex - 1) == ' ') - && QueryFilteringUtil.getDigit(name.charAt(nameIndex)) == query.charAt(initialsBolded)) { + && QueryFilteringUtil.getDigit(name.charAt(nameIndex), context) + == query.charAt(initialsBolded)) { boldedInitials.setSpan( new StyleSpan(Typeface.BOLD), nameIndex, diff --git a/java/com/android/dialer/searchfragment/common/QueryFilteringUtil.java b/java/com/android/dialer/searchfragment/common/QueryFilteringUtil.java index 6b5cea88d..1ecb486d2 100644 --- a/java/com/android/dialer/searchfragment/common/QueryFilteringUtil.java +++ b/java/com/android/dialer/searchfragment/common/QueryFilteringUtil.java @@ -16,14 +16,24 @@ package com.android.dialer.searchfragment.common; +import android.content.Context; import android.support.annotation.NonNull; +import android.support.v4.util.SimpleArrayMap; import android.telephony.PhoneNumberUtils; import android.text.TextUtils; +import com.android.dialer.dialpadview.DialpadCharMappings; import java.util.regex.Pattern; /** Utility class for filtering, comparing and handling strings and queries. */ public class QueryFilteringUtil { + /** + * The default character-digit map that will be used to find the digit associated with a given + * character on a T9 keyboard. + */ + private static final SimpleArrayMap DEFAULT_CHAR_TO_DIGIT_MAP = + DialpadCharMappings.getDefaultCharToKeyMap(); + /** Matches strings with "-", "(", ")", 2-9 of at least length one. */ private static final Pattern T9_PATTERN = Pattern.compile("[\\-()2-9]+"); @@ -38,15 +48,29 @@ public class QueryFilteringUtil { *
  • #nameMatchesT9Query("56", "Jessica Jones") returns true, 56 -> 'Jo' *
  • #nameMatchesT9Query("7", "Jessica Jones") returns false, no names start with P,Q,R or S * + * + *

    When the 1st language preference uses a non-Latin alphabet (e.g., Russian) and the character + * mappings for the alphabet is defined in {@link DialpadCharMappings}, the Latin alphabet will be + * used first to check if the name matches the query. If they don't match, the non-Latin alphabet + * will be used. + * + *

    Examples (when the 1st language preference is Russian): + * + *

      + *
    • #nameMatchesT9Query("7", "John Smith") returns true, 7 -> 'S' + *
    • #nameMatchesT9Query("7", "Павел Чехов") returns true, 7 -> 'Ч' + *
    • #nameMatchesT9Query("77", "Pavel Чехов") returns true, 7 -> 'P' (in the Latin alphabet), + * 7 -> 'Ч' (in the Russian alphabet) + *
    */ - public static boolean nameMatchesT9Query(String query, String name) { + public static boolean nameMatchesT9Query(String query, String name, Context context) { if (!T9_PATTERN.matcher(query).matches()) { return false; } query = digitsOnly(query); Pattern pattern = Pattern.compile("(^|\\s)" + Pattern.quote(query)); - if (pattern.matcher(getT9Representation(name)).find()) { + if (pattern.matcher(getT9Representation(name, context)).find()) { // query matches the start of a T9 name (i.e. 75 -> "Jessica [Jo]nes") return true; } @@ -61,7 +85,7 @@ public class QueryFilteringUtil { continue; } - if (getDigit(names[i].charAt(0)) == query.charAt(queryIndex)) { + if (getDigit(names[i].charAt(0), context) == query.charAt(queryIndex)) { queryIndex++; } } @@ -106,11 +130,17 @@ public class QueryFilteringUtil { return digitsOnly(number).indexOf(digitsOnly(query)); } - // Returns string with letters replaced with their T9 representation. - static String getT9Representation(String s) { + /** + * Replaces characters in the given string with their T9 representations. + * + * @param s The original string + * @param context The context + * @return The original string with characters replaced with T9 representations. + */ + static String getT9Representation(String s, Context context) { StringBuilder builder = new StringBuilder(s.length()); for (char c : s.toLowerCase().toCharArray()) { - builder.append(getDigit(c)); + builder.append(getDigit(c, context)); } return builder.toString(); } @@ -127,45 +157,26 @@ public class QueryFilteringUtil { return sb.toString(); } - // Returns the T9 representation of a lower case character, otherwise returns the character. - static char getDigit(char c) { - switch (c) { - case 'a': - case 'b': - case 'c': - return '2'; - case 'd': - case 'e': - case 'f': - return '3'; - case 'g': - case 'h': - case 'i': - return '4'; - case 'j': - case 'k': - case 'l': - return '5'; - case 'm': - case 'n': - case 'o': - return '6'; - case 'p': - case 'q': - case 'r': - case 's': - return '7'; - case 't': - case 'u': - case 'v': - return '8'; - case 'w': - case 'x': - case 'y': - case 'z': - return '9'; - default: - return c; + /** + * Returns the digit on a T9 keyboard which is associated with the given lower case character. + * + *

    The default character-key mapping will be used first to find a digit. If no digit is found, + * try the mapping of the current default locale if it is defined in {@link DialpadCharMappings}. + * If the second attempt fails, return the original character. + */ + static char getDigit(char c, Context context) { + Character digit = DEFAULT_CHAR_TO_DIGIT_MAP.get(c); + if (digit != null) { + return digit; + } + + SimpleArrayMap charToKeyMap = + DialpadCharMappings.getCharToKeyMap(context); + if (charToKeyMap != null) { + digit = charToKeyMap.get(c); + return digit != null ? digit : c; } + + return c; } } diff --git a/java/com/android/dialer/searchfragment/cp2/ContactFilterCursor.java b/java/com/android/dialer/searchfragment/cp2/ContactFilterCursor.java index 84c22a2cf..df67b762f 100644 --- a/java/com/android/dialer/searchfragment/cp2/ContactFilterCursor.java +++ b/java/com/android/dialer/searchfragment/cp2/ContactFilterCursor.java @@ -17,6 +17,7 @@ package com.android.dialer.searchfragment.cp2; import android.content.ContentResolver; +import android.content.Context; import android.database.CharArrayBuffer; import android.database.ContentObserver; import android.database.Cursor; @@ -44,7 +45,7 @@ import java.util.Set; * Wrapper for a cursor containing all on device contacts. * *

    This cursor removes duplicate phone numbers associated with the same contact and can filter - * contacts based on a query by calling {@link #filter(String)}. + * contacts based on a query by calling {@link #filter(String, Context)}. */ final class ContactFilterCursor implements Cursor { @@ -72,10 +73,11 @@ final class ContactFilterCursor implements Cursor { /** * @param cursor with projection {@link Projections#CP2_PROJECTION}. * @param query to filter cursor results. + * @param context of the app. */ - ContactFilterCursor(Cursor cursor, @Nullable String query) { + ContactFilterCursor(Cursor cursor, @Nullable String query, Context context) { this.cursor = createCursor(cursor); - filter(query); + filter(query, context); } /** @@ -238,7 +240,7 @@ final class ContactFilterCursor implements Cursor { *

  • Its company contains the query * */ - public void filter(@Nullable String query) { + public void filter(@Nullable String query, Context context) { if (query == null) { query = ""; } @@ -253,7 +255,7 @@ final class ContactFilterCursor implements Cursor { String companyName = cursor.getString(Projections.COMPANY_NAME); String nickName = cursor.getString(Projections.NICKNAME); if (TextUtils.isEmpty(query) - || QueryFilteringUtil.nameMatchesT9Query(query, name) + || QueryFilteringUtil.nameMatchesT9Query(query, name, context) || QueryFilteringUtil.numberMatchesNumberQuery(query, number) || QueryFilteringUtil.nameContainsQuery(query, name) || QueryFilteringUtil.nameContainsQuery(query, companyName) diff --git a/java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java b/java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java index c09396c72..386ab3a6b 100644 --- a/java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java +++ b/java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java @@ -104,7 +104,7 @@ public final class SearchContactViewHolder extends ViewHolder implements OnClick : context.getString( com.android.contacts.common.R.string.call_subject_type_and_number, label, number); - nameOrNumberView.setText(QueryBoldingUtil.getNameWithQueryBolded(query, name)); + nameOrNumberView.setText(QueryBoldingUtil.getNameWithQueryBolded(query, name, context)); numberView.setText(QueryBoldingUtil.getNumberWithQueryBolded(query, secondaryInfo)); setCallToAction(cursor, query); diff --git a/java/com/android/dialer/searchfragment/cp2/SearchContactsCursor.java b/java/com/android/dialer/searchfragment/cp2/SearchContactsCursor.java index 508ca7f57..7697e0520 100644 --- a/java/com/android/dialer/searchfragment/cp2/SearchContactsCursor.java +++ b/java/com/android/dialer/searchfragment/cp2/SearchContactsCursor.java @@ -32,17 +32,19 @@ import com.android.dialer.searchfragment.common.SearchCursor; final class SearchContactsCursor extends MergeCursor implements SearchCursor { private final ContactFilterCursor contactFilterCursor; + private final Context context; static SearchContactsCursor newInstance( Context context, ContactFilterCursor contactFilterCursor) { MatrixCursor headerCursor = new MatrixCursor(HEADER_PROJECTION); headerCursor.addRow(new String[] {context.getString(R.string.all_contacts)}); - return new SearchContactsCursor(new Cursor[] {headerCursor, contactFilterCursor}); + return new SearchContactsCursor(new Cursor[] {headerCursor, contactFilterCursor}, context); } - private SearchContactsCursor(Cursor[] cursors) { + private SearchContactsCursor(Cursor[] cursors, Context context) { super(cursors); - contactFilterCursor = (ContactFilterCursor) cursors[1]; + this.contactFilterCursor = (ContactFilterCursor) cursors[1]; + this.context = context; } @Override @@ -52,7 +54,7 @@ final class SearchContactsCursor extends MergeCursor implements SearchCursor { @Override public boolean updateQuery(@Nullable String query) { - contactFilterCursor.filter(query); + contactFilterCursor.filter(query, context); return true; } diff --git a/java/com/android/dialer/searchfragment/cp2/SearchContactsCursorLoader.java b/java/com/android/dialer/searchfragment/cp2/SearchContactsCursorLoader.java index d3abbffca..35518019e 100644 --- a/java/com/android/dialer/searchfragment/cp2/SearchContactsCursorLoader.java +++ b/java/com/android/dialer/searchfragment/cp2/SearchContactsCursorLoader.java @@ -61,7 +61,7 @@ public final class SearchContactsCursorLoader extends CursorLoader { // All contacts Cursor cursor = super.loadInBackground(); // Filtering logic - ContactFilterCursor contactFilterCursor = new ContactFilterCursor(cursor, query); + ContactFilterCursor contactFilterCursor = new ContactFilterCursor(cursor, query, getContext()); // Header logic return SearchContactsCursor.newInstance(getContext(), contactFilterCursor); } diff --git a/java/com/android/dialer/searchfragment/nearbyplaces/NearbyPlaceViewHolder.java b/java/com/android/dialer/searchfragment/nearbyplaces/NearbyPlaceViewHolder.java index 5d5188059..2e1fd5e9d 100644 --- a/java/com/android/dialer/searchfragment/nearbyplaces/NearbyPlaceViewHolder.java +++ b/java/com/android/dialer/searchfragment/nearbyplaces/NearbyPlaceViewHolder.java @@ -64,8 +64,8 @@ public final class NearbyPlaceViewHolder extends RecyclerView.ViewHolder 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)); + placeName.setText(QueryBoldingUtil.getNameWithQueryBolded(query, name, context)); + placeAddress.setText(QueryBoldingUtil.getNameWithQueryBolded(query, address, context)); String photoUri = cursor.getString(Projections.PHOTO_URI); ContactPhotoManager.getInstance(context) .loadDialerThumbnailOrPhoto( diff --git a/java/com/android/dialer/searchfragment/remote/RemoteContactViewHolder.java b/java/com/android/dialer/searchfragment/remote/RemoteContactViewHolder.java index 8a02eb9b9..339855fbb 100644 --- a/java/com/android/dialer/searchfragment/remote/RemoteContactViewHolder.java +++ b/java/com/android/dialer/searchfragment/remote/RemoteContactViewHolder.java @@ -72,8 +72,8 @@ public final class RemoteContactViewHolder extends RecyclerView.ViewHolder : context.getString( com.android.contacts.common.R.string.call_subject_type_and_number, label, number); - nameView.setText(QueryBoldingUtil.getNameWithQueryBolded(query, name)); - numberView.setText(QueryBoldingUtil.getNameWithQueryBolded(query, secondaryInfo)); + nameView.setText(QueryBoldingUtil.getNameWithQueryBolded(query, name, context)); + numberView.setText(QueryBoldingUtil.getNameWithQueryBolded(query, secondaryInfo, context)); if (shouldShowPhoto(cursor)) { nameView.setVisibility(View.VISIBLE); -- cgit v1.2.3