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.support.annotation.NonNull; 20 import android.telephony.PhoneNumberUtils; 21 import android.text.TextUtils; 22 import java.util.regex.Pattern; 23 24 /** Utility class for filtering, comparing and handling strings and queries. */ 25 public class QueryFilteringUtil { 26 27 /** Matches strings with "-", "(", ")", 2-9 of at least length one. */ 28 static final Pattern T9_PATTERN = Pattern.compile("[\\-()2-9]+"); 29 30 /** 31 * @return true if the query is of T9 format and the name's T9 representation belongs to the 32 * query; false otherwise. 33 */ 34 public static boolean nameMatchesT9Query(String query, String name) { 35 if (!T9_PATTERN.matcher(query).matches()) { 36 return false; 37 } 38 39 // Substring 40 if (indexOfQueryNonDigitsIgnored(query, getT9Representation(name)) != -1) { 41 return true; 42 } 43 44 // Check matches initials 45 // TODO investigate faster implementation 46 query = digitsOnly(query); 47 int queryIndex = 0; 48 49 String[] names = name.toLowerCase().split("\\s"); 50 for (int i = 0; i < names.length && queryIndex < query.length(); i++) { 51 if (TextUtils.isEmpty(names[i])) { 52 continue; 53 } 54 55 if (getDigit(names[i].charAt(0)) == query.charAt(queryIndex)) { 56 queryIndex++; 57 } 58 } 59 60 return queryIndex == query.length(); 61 } 62 63 /** @return true if the number belongs to the query. */ 64 public static boolean numberMatchesNumberQuery(String query, String number) { 65 return PhoneNumberUtils.isGlobalPhoneNumber(query) 66 && indexOfQueryNonDigitsIgnored(query, number) != -1; 67 } 68 69 /** 70 * Checks if query is contained in number while ignoring all characters in both that are not 71 * digits (i.e. {@link Character#isDigit(char)} returns false). 72 * 73 * @return index where query is found with all non-digits removed, -1 if it's not found. 74 */ 75 static int indexOfQueryNonDigitsIgnored(@NonNull String query, @NonNull String number) { 76 return digitsOnly(number).indexOf(digitsOnly(query)); 77 } 78 79 // Returns string with letters replaced with their T9 representation. 80 static String getT9Representation(String s) { 81 StringBuilder builder = new StringBuilder(s.length()); 82 for (char c : s.toLowerCase().toCharArray()) { 83 builder.append(getDigit(c)); 84 } 85 return builder.toString(); 86 } 87 88 /** @return String s with only digits recognized by Character#isDigit() remaining */ 89 public static String digitsOnly(String s) { 90 StringBuilder sb = new StringBuilder(); 91 for (int i = 0; i < s.length(); i++) { 92 char c = s.charAt(i); 93 if (Character.isDigit(c)) { 94 sb.append(c); 95 } 96 } 97 return sb.toString(); 98 } 99 100 // Returns the T9 representation of a lower case character, otherwise returns the character. 101 static char getDigit(char c) { 102 switch (c) { 103 case 'a': 104 case 'b': 105 case 'c': 106 return '2'; 107 case 'd': 108 case 'e': 109 case 'f': 110 return '3'; 111 case 'g': 112 case 'h': 113 case 'i': 114 return '4'; 115 case 'j': 116 case 'k': 117 case 'l': 118 return '5'; 119 case 'm': 120 case 'n': 121 case 'o': 122 return '6'; 123 case 'p': 124 case 'q': 125 case 'r': 126 case 's': 127 return '7'; 128 case 't': 129 case 'u': 130 case 'v': 131 return '8'; 132 case 'w': 133 case 'x': 134 case 'y': 135 case 'z': 136 return '9'; 137 default: 138 return c; 139 } 140 } 141 } 142