diff options
Diffstat (limited to 'src/com/android/dialer/dialpad/SmartDialTrie.java')
-rw-r--r-- | src/com/android/dialer/dialpad/SmartDialTrie.java | 375 |
1 files changed, 368 insertions, 7 deletions
diff --git a/src/com/android/dialer/dialpad/SmartDialTrie.java b/src/com/android/dialer/dialpad/SmartDialTrie.java index 981926717..9dfc0c95a 100644 --- a/src/com/android/dialer/dialpad/SmartDialTrie.java +++ b/src/com/android/dialer/dialpad/SmartDialTrie.java @@ -20,27 +20,58 @@ import android.text.TextUtils; import com.android.dialer.dialpad.SmartDialCache.ContactNumber; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; import com.google.common.collect.Lists; import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; /** * Prefix trie where the only allowed characters are the characters '0' to '9'. Multiple contacts - * can occupy the same nodes. Provides functions to get all contacts that lie on or below a node. - * This is useful for retrieving all contacts that start with that prefix. + * can occupy the same nodes. + * + * <p>Provides functions to get all contacts that lie on or below a node. + * This is useful for retrieving all contacts that start with that prefix.</p> + * + * <p>Also contains special logic to handle NANP numbers in the case that the user is from a region + * that uses NANP numbers.</p> */ public class SmartDialTrie { final Node mRoot = new Node(); private int mSize = 0; private final char[] mCharacterMap; + private final boolean mFormatNanp; + + // Static set of all possible country codes in the world + public static Set<String> sCountryCodes = null; public SmartDialTrie() { // Use the latin letter to digit map by default if none provided - this(SmartDialNameMatcher.LATIN_LETTERS_TO_DIGITS); + this(SmartDialNameMatcher.LATIN_LETTERS_TO_DIGITS, false); + } + + /** + * Creates a new SmartDialTrie. + * + * @param formatNanp True if inserted numbers are to be treated as NANP numbers + * such that numbers are automatically broken up by country prefix and area code. + */ + @VisibleForTesting + public SmartDialTrie(boolean formatNanp) { + this(SmartDialNameMatcher.LATIN_LETTERS_TO_DIGITS, formatNanp); } - public SmartDialTrie(char[] charMap) { + /** + * Creates a new SmartDialTrie. + * + * @param charMap Mapping of characters to digits to use when inserting names into the trie. + * @param formatNanp True if inserted numbers are to be treated as NANP numbers + * such that numbers are automatically broken up by country prefix and area code. + */ + public SmartDialTrie(char[] charMap, boolean formatNanp) { mCharacterMap = charMap; + mFormatNanp = formatNanp; } /** @@ -90,10 +121,100 @@ public class SmartDialTrie { putForPrefix(contact, mRoot, toByteArray(contact.displayName), 0, contact.displayName.length(), true, true); // We don't need to do the same for phone numbers since we only make one pass over them. - putNumber(contact, contact.phoneNumber); + // Strip the calling code from the phone number here + if (!TextUtils.isEmpty(contact.phoneNumber)) { + // Handle country codes for numbers with a + prefix + final int offset = getOffsetWithoutCountryCode(contact.phoneNumber); + if (offset > 0) { + putNumber(contact, contact.phoneNumber, offset); + } else if (mFormatNanp) { + // Special case handling for NANP numbers (1-xxx-xxx-xxxx) + final String stripped = SmartDialNameMatcher.normalizeNumber(contact.phoneNumber); + if (!TextUtils.isEmpty(stripped)) { + int trunkPrefixOffset = 0; + if (stripped.charAt(0) == '1') { + // If the number starts with 1, we can assume its the trunk prefix. + trunkPrefixOffset = 1; + } + if (stripped.length() == (10 + trunkPrefixOffset)) { + // Valid NANP number + if (trunkPrefixOffset != 0) { + // Add the digits that follow the 1st digit (trunk prefix) + // If trunkPrefixOffset is 0, we will add the number as is anyway, so + // don't bother. + putNumber(contact, stripped, trunkPrefixOffset); + } + // Add the digits that follow the next 3 digits (area code) + putNumber(contact, stripped, 3 + trunkPrefixOffset); + } + } + } + putNumber(contact, contact.phoneNumber); + } mSize++; } + public static int getOffsetWithoutCountryCode(String number) { + if (!TextUtils.isEmpty(number)) { + if (number.charAt(0) == '+') { + // check for international code here + for (int i = 1; i <= 1 + 3; i++) { + if (number.length() <= i) break; + if (isValidCountryCode(number.substring(1, i))) { + return i; + } + } + } + } + return -1; + } + + /** + * Used by SmartDialNameMatcher to determine which character in the phone number to start + * the matching process from for a NANP formatted number. + * + * @param number Raw phone number + * @return An empty array if the provided number does not appear to be an NANP number, + * and an array containing integer offsets for the number (starting after the '1' prefix, + * and the area code prefix respectively. + */ + public static int[] getOffsetForNANPNumbers(String number) { + int validDigits = 0; + boolean hasPrefix = false; + int firstOffset = 0; // Tracks the location of the first digit after the '1' prefix + int secondOffset = 0; // Tracks the location of the first digit after the area code + for (int i = 0; i < number.length(); i++) { + final char ch = number.charAt(i); + if (ch >= '0' && ch <= '9') { + if (validDigits == 0) { + // Check the first digit to see if it is 1 + if (ch == '1') { + if (hasPrefix) { + // Prefix has two '1's in a row. Invalid number, since area codes + // cannot start with 1, so just bail + break; + } + hasPrefix = true; + continue; + } + } + validDigits++; + if (validDigits == 1) { + // Found the first digit after the country code + firstOffset = i; + } else if (validDigits == 4) { + // Found the first digit after the area code + secondOffset = i; + } + } + + } + if (validDigits == 10) { + return hasPrefix ? new int[] {firstOffset, secondOffset} : new int[] {secondOffset}; + } + return new int[0]; + } + @VisibleForTesting /* package */ byte[] toByteArray(CharSequence chars) { final int length = chars.length(); @@ -115,12 +236,24 @@ public class SmartDialTrie { * * @param contact - Contact to add to the trie * @param phoneNumber - Phone number of the contact + */ + private void putNumber(ContactNumber contact, String phoneNumber) { + putNumber(contact, phoneNumber, 0); + } + + /** + * Puts a phone number and its associated contact into the prefix trie. + * + * @param contact - Contact to add to the trie + * @param phoneNumber - Phone number of the contact + * @param offset - The nth character of the phone number to start from */ - public void putNumber(ContactNumber contact, String phoneNumber) { + private void putNumber(ContactNumber contact, String phoneNumber, int offSet) { + Preconditions.checkArgument(offSet < phoneNumber.length()); Node current = mRoot; final int length = phoneNumber.length(); char ch; - for (int i = 0; i < length; i++) { + for (int i = offSet; i < length; i++) { ch = phoneNumber.charAt(i); if (ch >= '0' && ch <= '9') { current = current.getChild(ch, true); @@ -183,6 +316,7 @@ public class SmartDialTrie { current.add(contact); } + @VisibleForTesting public int size() { return mSize; } @@ -247,4 +381,231 @@ public class SmartDialTrie { return mContents; } } + + private static boolean isValidCountryCode(String countryCode) { + if (sCountryCodes == null) { + sCountryCodes = initCountryCodes(); + } + return sCountryCodes.contains(countryCode); + } + + private static Set<String> initCountryCodes() { + final HashSet<String> result = new HashSet<String>(); + result.add("1"); + result.add("7"); + result.add("20"); + result.add("27"); + result.add("30"); + result.add("31"); + result.add("32"); + result.add("33"); + result.add("34"); + result.add("36"); + result.add("39"); + result.add("40"); + result.add("41"); + result.add("43"); + result.add("44"); + result.add("45"); + result.add("46"); + result.add("47"); + result.add("48"); + result.add("49"); + result.add("51"); + result.add("52"); + result.add("53"); + result.add("54"); + result.add("55"); + result.add("56"); + result.add("57"); + result.add("58"); + result.add("60"); + result.add("61"); + result.add("62"); + result.add("63"); + result.add("64"); + result.add("65"); + result.add("66"); + result.add("81"); + result.add("82"); + result.add("84"); + result.add("86"); + result.add("90"); + result.add("91"); + result.add("92"); + result.add("93"); + result.add("94"); + result.add("95"); + result.add("98"); + result.add("211"); + result.add("212"); + result.add("213"); + result.add("216"); + result.add("218"); + result.add("220"); + result.add("221"); + result.add("222"); + result.add("223"); + result.add("224"); + result.add("225"); + result.add("226"); + result.add("227"); + result.add("228"); + result.add("229"); + result.add("230"); + result.add("231"); + result.add("232"); + result.add("233"); + result.add("234"); + result.add("235"); + result.add("236"); + result.add("237"); + result.add("238"); + result.add("239"); + result.add("240"); + result.add("241"); + result.add("242"); + result.add("243"); + result.add("244"); + result.add("245"); + result.add("246"); + result.add("247"); + result.add("248"); + result.add("249"); + result.add("250"); + result.add("251"); + result.add("252"); + result.add("253"); + result.add("254"); + result.add("255"); + result.add("256"); + result.add("257"); + result.add("258"); + result.add("260"); + result.add("261"); + result.add("262"); + result.add("263"); + result.add("264"); + result.add("265"); + result.add("266"); + result.add("267"); + result.add("268"); + result.add("269"); + result.add("290"); + result.add("291"); + result.add("297"); + result.add("298"); + result.add("299"); + result.add("350"); + result.add("351"); + result.add("352"); + result.add("353"); + result.add("354"); + result.add("355"); + result.add("356"); + result.add("357"); + result.add("358"); + result.add("359"); + result.add("370"); + result.add("371"); + result.add("372"); + result.add("373"); + result.add("374"); + result.add("375"); + result.add("376"); + result.add("377"); + result.add("378"); + result.add("379"); + result.add("380"); + result.add("381"); + result.add("382"); + result.add("385"); + result.add("386"); + result.add("387"); + result.add("389"); + result.add("420"); + result.add("421"); + result.add("423"); + result.add("500"); + result.add("501"); + result.add("502"); + result.add("503"); + result.add("504"); + result.add("505"); + result.add("506"); + result.add("507"); + result.add("508"); + result.add("509"); + result.add("590"); + result.add("591"); + result.add("592"); + result.add("593"); + result.add("594"); + result.add("595"); + result.add("596"); + result.add("597"); + result.add("598"); + result.add("599"); + result.add("670"); + result.add("672"); + result.add("673"); + result.add("674"); + result.add("675"); + result.add("676"); + result.add("677"); + result.add("678"); + result.add("679"); + result.add("680"); + result.add("681"); + result.add("682"); + result.add("683"); + result.add("685"); + result.add("686"); + result.add("687"); + result.add("688"); + result.add("689"); + result.add("690"); + result.add("691"); + result.add("692"); + result.add("800"); + result.add("808"); + result.add("850"); + result.add("852"); + result.add("853"); + result.add("855"); + result.add("856"); + result.add("870"); + result.add("878"); + result.add("880"); + result.add("881"); + result.add("882"); + result.add("883"); + result.add("886"); + result.add("888"); + result.add("960"); + result.add("961"); + result.add("962"); + result.add("963"); + result.add("964"); + result.add("965"); + result.add("966"); + result.add("967"); + result.add("968"); + result.add("970"); + result.add("971"); + result.add("972"); + result.add("973"); + result.add("974"); + result.add("975"); + result.add("976"); + result.add("977"); + result.add("979"); + result.add("992"); + result.add("993"); + result.add("994"); + result.add("995"); + result.add("996"); + result.add("998"); + return result; + } } |