1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.dialer.searchfragment.common; 18 19 import android.content.Context; 20 import android.graphics.Typeface; 21 import android.support.annotation.NonNull; 22 import android.support.annotation.Nullable; 23 import android.text.SpannableString; 24 import android.text.Spanned; 25 import android.text.TextUtils; 26 import android.text.style.StyleSpan; 27 import com.android.dialer.common.LogUtil; 28 import java.util.regex.Matcher; 29 import java.util.regex.Pattern; 30 31 /** Utility class for handling bolding queries contained in string. */ 32 public class QueryBoldingUtil { 33 34 /** 35 * Compares a name and query and returns a {@link CharSequence} with bolded characters. 36 * 37 * <p>Some example of matches: 38 * 39 * <ul> 40 * <li>"query" would bold "John [query] Smith" 41 * <li>"222" would bold "[AAA] Mom" 42 * <li>"222" would bold "[A]llen [A]lex [A]aron" 43 * <li>"2226" would bold "[AAA M]om" 44 * </ul> 45 * 46 * <p>Some examples of non-matches: 47 * 48 * <ul> 49 * <li>"ss" would not match "Jessica Jones" 50 * <li>"77" would not match "Jessica Jones" 51 * </ul> 52 * 53 * @param query containing any characters 54 * @param name of a contact/string that query will compare to 55 * @param context of the app 56 * @return name with query bolded if query can be found in the name. 57 */ 58 public static CharSequence getNameWithQueryBolded( 59 @Nullable String query, @NonNull String name, @NonNull Context context) { 60 if (TextUtils.isEmpty(query)) { 61 return name; 62 } 63 64 if (!QueryFilteringUtil.nameMatchesT9Query(query, name, context)) { 65 Pattern pattern = Pattern.compile("(^|\\s)" + Pattern.quote(query.toLowerCase())); 66 Matcher matcher = pattern.matcher(name.toLowerCase()); 67 if (matcher.find()) { 68 // query matches the start of a name (i.e. "jo" -> "Jessica [Jo]nes") 69 return getBoldedString(name, matcher.start(), query.length()); 70 } else { 71 // query not found in name 72 return name; 73 } 74 } 75 76 int indexOfT9Match = QueryFilteringUtil.getIndexOfT9Substring(query, name, context); 77 if (indexOfT9Match != -1) { 78 // query matches the start of a T9 name (i.e. 75 -> "Jessica [Jo]nes") 79 int numBolded = query.length(); 80 81 // Bold an extra character for each non-letter 82 for (int i = indexOfT9Match; i <= indexOfT9Match + numBolded && i < name.length(); i++) { 83 if (!Character.isLetter(name.charAt(i))) { 84 numBolded++; 85 } 86 } 87 return getBoldedString(name, indexOfT9Match, numBolded); 88 } else { 89 // query match the T9 initials (i.e. 222 -> "[A]l [B]ob [C]harlie") 90 return getNameWithInitialsBolded(query, name, context); 91 } 92 } 93 94 private static CharSequence getNameWithInitialsBolded( 95 String query, String name, Context context) { 96 SpannableString boldedInitials = new SpannableString(name); 97 name = name.toLowerCase(); 98 int initialsBolded = 0; 99 int nameIndex = -1; 100 101 while (++nameIndex < name.length() && initialsBolded < query.length()) { 102 if ((nameIndex == 0 || name.charAt(nameIndex - 1) == ' ') 103 && QueryFilteringUtil.getDigit(name.charAt(nameIndex), context) 104 == query.charAt(initialsBolded)) { 105 boldedInitials.setSpan( 106 new StyleSpan(Typeface.BOLD), 107 nameIndex, 108 nameIndex + 1, 109 Spanned.SPAN_INCLUSIVE_INCLUSIVE); 110 initialsBolded++; 111 } 112 } 113 return boldedInitials; 114 } 115 116 /** 117 * Compares a number and a query and returns a {@link CharSequence} with bolded characters. 118 * 119 * <ul> 120 * <li>"123" would bold "(650)34[1-23]24" 121 * <li>"123" would bold "+1([123])111-2222 122 * </ul> 123 * 124 * @param query containing only numbers and phone number related characters "(", ")", "-", "+" 125 * @param number phone number of a contact that the query will compare to. 126 * @return number with query bolded if query can be found in the number. 127 */ 128 public static CharSequence getNumberWithQueryBolded( 129 @Nullable String query, @NonNull String number) { 130 if (TextUtils.isEmpty(query) || !QueryFilteringUtil.numberMatchesNumberQuery(query, number)) { 131 return number; 132 } 133 134 int index = QueryFilteringUtil.indexOfQueryNonDigitsIgnored(query, number); 135 int boldedCharacters = query.length(); 136 137 for (char c : query.toCharArray()) { 138 if (!Character.isDigit(c)) { 139 boldedCharacters--; 140 } 141 } 142 143 for (int i = 0; i < index + boldedCharacters; i++) { 144 if (!Character.isDigit(number.charAt(i))) { 145 if (i <= index) { 146 index++; 147 } else { 148 boldedCharacters++; 149 } 150 } 151 } 152 return getBoldedString(number, index, boldedCharacters); 153 } 154 155 private static SpannableString getBoldedString(String s, int index, int numBolded) { 156 if (numBolded + index > s.length()) { 157 LogUtil.e( 158 "QueryBoldingUtil#getBoldedString", 159 "number of bolded characters exceeded length of string."); 160 numBolded = s.length() - index; 161 } 162 SpannableString span = new SpannableString(s); 163 span.setSpan( 164 new StyleSpan(Typeface.BOLD), index, index + numBolded, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); 165 return span; 166 } 167 } 168