summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/com/android/dialer/dialpad/SmartDialCache.java96
-rw-r--r--src/com/android/dialer/dialpad/SmartDialLoaderTask.java6
-rw-r--r--src/com/android/dialer/dialpad/SmartDialNameMatcher.java60
-rw-r--r--src/com/android/dialer/dialpad/SmartDialTrie.java375
-rw-r--r--tests/src/com/android/dialer/dialpad/SmartDialCacheTest.java56
-rw-r--r--tests/src/com/android/dialer/dialpad/SmartDialNameMatcherTest.java77
-rw-r--r--tests/src/com/android/dialer/dialpad/SmartDialTrieTest.java166
7 files changed, 781 insertions, 55 deletions
diff --git a/src/com/android/dialer/dialpad/SmartDialCache.java b/src/com/android/dialer/dialpad/SmartDialCache.java
index 79cc72763..7a11b15d8 100644
--- a/src/com/android/dialer/dialpad/SmartDialCache.java
+++ b/src/com/android/dialer/dialpad/SmartDialCache.java
@@ -19,22 +19,27 @@ package com.android.dialer.dialpad;
import static com.android.dialer.dialpad.SmartDialAdapter.LOG_TAG;
import android.content.Context;
-import android.database.ContentObserver;
+import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
-import android.os.Handler;
+import android.preference.PreferenceManager;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.Directory;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
import android.util.Log;
import com.android.contacts.common.util.StopWatch;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -134,12 +139,23 @@ public class SmartDialCache {
+ Data.IS_PRIMARY + " DESC";
}
+ // Static set used to determine which countries use NANP numbers
+ public static Set<String> sNanpCountries = null;
+
private SmartDialTrie mContactsCache;
private static AtomicInteger mCacheStatus;
private final int mNameDisplayOrder;
private final Context mContext;
private final static Object mLock = new Object();
+ /** The country code of the user's sim card obtained by calling getSimCountryIso*/
+ private static final String PREF_USER_SIM_COUNTRY_CODE =
+ "DialtactsActivity_user_sim_country_code";
+ private static final String PREF_USER_SIM_COUNTRY_CODE_DEFAULT = null;
+
+ private static String sUserSimCountryCode = PREF_USER_SIM_COUNTRY_CODE_DEFAULT;
+ private static boolean sUserInNanpRegion = false;
+
public static final int CACHE_NEEDS_RECACHE = 1;
public static final int CACHE_IN_PROGRESS = 2;
public static final int CACHE_COMPLETED = 3;
@@ -151,6 +167,27 @@ public class SmartDialCache {
Preconditions.checkNotNull(context, "Context must not be null");
mContext = context.getApplicationContext();
mCacheStatus = new AtomicInteger(CACHE_NEEDS_RECACHE);
+
+ final TelephonyManager manager = (TelephonyManager) context.getSystemService(
+ Context.TELEPHONY_SERVICE);
+ if (manager != null) {
+ sUserSimCountryCode = manager.getSimCountryIso();
+ }
+
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+
+ if (sUserSimCountryCode != null) {
+ // Update shared preferences with the latest country obtained from getSimCountryIso
+ prefs.edit().putString(PREF_USER_SIM_COUNTRY_CODE, sUserSimCountryCode).apply();
+ } else {
+ // Couldn't get the country from getSimCountryIso. Maybe we are in airplane mode.
+ // Try to load the settings, if any from SharedPreferences.
+ sUserSimCountryCode = prefs.getString(PREF_USER_SIM_COUNTRY_CODE,
+ PREF_USER_SIM_COUNTRY_CODE_DEFAULT);
+ }
+
+ sUserInNanpRegion = isCountryNanp(sUserSimCountryCode);
+
}
private static SmartDialCache instance;
@@ -200,7 +237,7 @@ public class SmartDialCache {
return;
}
final SmartDialTrie cache = new SmartDialTrie(
- SmartDialNameMatcher.LATIN_LETTERS_TO_DIGITS);
+ SmartDialNameMatcher.LATIN_LETTERS_TO_DIGITS, sUserInNanpRegion);
try {
c.moveToPosition(-1);
int affinityCount = 0;
@@ -259,8 +296,7 @@ public class SmartDialCache {
// the contacts again.
if (mContactsCache == null) {
cacheContacts(mContext);
- return (mContactsCache == null) ? new SmartDialTrie(
- SmartDialNameMatcher.LATIN_LETTERS_TO_DIGITS) : mContactsCache;
+ return (mContactsCache == null) ? new SmartDialTrie() : mContactsCache;
} else {
// After waiting for the lock on mLock to be released, mContactsCache is now
// non-null due to the completion of the caching thread (Scenario 2). Go ahead
@@ -313,4 +349,54 @@ public class SmartDialCache {
}
}
+
+ public boolean getUserInNanpRegion() {
+ return sUserInNanpRegion;
+ }
+
+ /**
+ * Indicates whether the given country uses NANP numbers
+ *
+ * @param country ISO 3166 country code (case doesn't matter)
+ * @return True if country uses NANP numbers (e.g. US, Canada), false otherwise
+ */
+ @VisibleForTesting
+ static boolean isCountryNanp(String country) {
+ if (TextUtils.isEmpty(country)) {
+ return false;
+ }
+ if (sNanpCountries == null) {
+ sNanpCountries = initNanpCountries();
+ }
+ return sNanpCountries.contains(country.toUpperCase());
+ }
+
+ private static Set<String> initNanpCountries() {
+ final HashSet<String> result = new HashSet<String>();
+ result.add("US"); // United States
+ result.add("CA"); // Canada
+ result.add("AS"); // American Samoa
+ result.add("AI"); // Anguilla
+ result.add("AG"); // Antigua and Barbuda
+ result.add("BS"); // Bahamas
+ result.add("BB"); // Barbados
+ result.add("BM"); // Bermuda
+ result.add("VG"); // British Virgin Islands
+ result.add("KY"); // Cayman Islands
+ result.add("DM"); // Dominica
+ result.add("DO"); // Dominican Republic
+ result.add("GD"); // Grenada
+ result.add("GU"); // Guam
+ result.add("JM"); // Jamaica
+ result.add("PR"); // Puerto Rico
+ result.add("MS"); // Montserrat
+ result.add("MP"); // Northern Mariana Islands
+ result.add("KN"); // Saint Kitts and Nevis
+ result.add("LC"); // Saint Lucia
+ result.add("VC"); // Saint Vincent and the Grenadines
+ result.add("TT"); // Trinidad and Tobago
+ result.add("TC"); // Turks and Caicos Islands
+ result.add("VI"); // U.S. Virgin Islands
+ return result;
+ }
}
diff --git a/src/com/android/dialer/dialpad/SmartDialLoaderTask.java b/src/com/android/dialer/dialpad/SmartDialLoaderTask.java
index c5fc65761..08c526503 100644
--- a/src/com/android/dialer/dialpad/SmartDialLoaderTask.java
+++ b/src/com/android/dialer/dialpad/SmartDialLoaderTask.java
@@ -93,6 +93,8 @@ public class SmartDialLoaderTask extends AsyncTask<String, Integer, List<SmartDi
private ArrayList<SmartDialEntry> getContactMatches() {
final SmartDialTrie trie = mContactsCache.getContacts();
+ final boolean matchNanp = mContactsCache.getUserInNanpRegion();
+
if (DEBUG) {
Log.d(LOG_TAG, "Size of cache: " + trie.size());
}
@@ -119,12 +121,14 @@ public class SmartDialLoaderTask extends AsyncTask<String, Integer, List<SmartDi
}
duplicates.add(contactMatch);
final boolean matches = mNameMatcher.matches(contact.displayName);
+
candidates.add(new SmartDialEntry(
contact.displayName,
Contacts.getLookupUri(contact.id, contact.lookupKey),
contact.phoneNumber,
mNameMatcher.getMatchPositions(),
- mNameMatcher.matchesNumber(contact.phoneNumber, mNameMatcher.getQuery())
+ SmartDialNameMatcher.matchesNumber(contact.phoneNumber,
+ mNameMatcher.getQuery(), matchNanp)
));
if (candidates.size() >= MAX_ENTRIES) {
break;
diff --git a/src/com/android/dialer/dialpad/SmartDialNameMatcher.java b/src/com/android/dialer/dialpad/SmartDialNameMatcher.java
index f805abff1..4d3948100 100644
--- a/src/com/android/dialer/dialpad/SmartDialNameMatcher.java
+++ b/src/com/android/dialer/dialpad/SmartDialNameMatcher.java
@@ -437,20 +437,54 @@ public class SmartDialNameMatcher {
}
/**
+ * Matches a phone number against a query, taking care of formatting characters and also
+ * taking into account country code prefixes and special NANP number treatment.
+ *
+ * @param phoneNumber - Raw phone number
+ * @param query - Normalized query (only contains numbers from 0-9)
+ * @param matchNanp - Whether or not to do special matching for NANP numbers
+ * @return {@literal null} if the number and the query don't match, a valid
+ * SmartDialMatchPosition with the matching positions otherwise
+ */
+ public static SmartDialMatchPosition matchesNumber(String phoneNumber, String query,
+ boolean matchNanp) {
+ // Try matching the number as is
+ SmartDialMatchPosition matchPos = matchesNumberWithOffset(phoneNumber, query, 0);
+ if (matchPos == null) {
+ // Try matching the number without the '+' prefix, if any
+ int offset = SmartDialTrie.getOffsetWithoutCountryCode(phoneNumber);
+ if (offset > 0) {
+ matchPos = matchesNumberWithOffset(phoneNumber, query, offset);
+ } else if (matchNanp) {
+ // Try matching NANP numbers
+ final int[] offsets = SmartDialTrie.getOffsetForNANPNumbers(phoneNumber);
+ for (int i = 0; i < offsets.length; i++) {
+ matchPos = matchesNumberWithOffset(phoneNumber, query, offsets[i]);
+ if (matchPos != null) break;
+ }
+ }
+ }
+ return matchPos;
+ }
+
+ /**
* Matches a phone number against a query, taking care of formatting characters
*
* @param phoneNumber - Raw phone number
* @param query - Normalized query (only contains numbers from 0-9)
+ * @param offset - The position in the number to start the match against (used to ignore
+ * leading prefixes/country codes)
* @return {@literal null} if the number and the query don't match, a valid
* SmartDialMatchPosition with the matching positions otherwise
*/
- public SmartDialMatchPosition matchesNumber(String phoneNumber, String query) {
+ private static SmartDialMatchPosition matchesNumberWithOffset(String phoneNumber, String query,
+ int offset) {
if (TextUtils.isEmpty(phoneNumber) || TextUtils.isEmpty(query)) {
return null;
}
int queryAt = 0;
- int numberAt = 0;
- for (int i = 0; i < phoneNumber.length(); i++) {
+ int numberAt = offset;
+ for (int i = offset; i < phoneNumber.length(); i++) {
if (queryAt == query.length()) {
break;
}
@@ -460,13 +494,25 @@ public class SmartDialNameMatcher {
return null;
}
queryAt++;
- numberAt++;
} else {
- numberAt++;
- continue;
+ if (queryAt == 0) {
+ // Found a separator before any part of the query was matched, so advance the
+ // offset to avoid prematurely highlighting separators before the rest of the
+ // query.
+ // E.g. don't highlight the first '-' if we're matching 1-510-111-1111 with
+ // '510'.
+ // However, if the current offset is 0, just include the beginning separators
+ // anyway, otherwise the highlighting ends up looking weird.
+ // E.g. if we're matching (510)-111-1111 with '510', we should include the
+ // first '('.
+ if (offset != 0) {
+ offset++;
+ }
+ }
}
+ numberAt++;
}
- return new SmartDialMatchPosition(0, numberAt);
+ return new SmartDialMatchPosition(0 + offset, numberAt);
}
/**
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;
+ }
}
diff --git a/tests/src/com/android/dialer/dialpad/SmartDialCacheTest.java b/tests/src/com/android/dialer/dialpad/SmartDialCacheTest.java
new file mode 100644
index 000000000..8d96edafc
--- /dev/null
+++ b/tests/src/com/android/dialer/dialpad/SmartDialCacheTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dialer.dialpad;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import junit.framework.TestCase;
+
+@SmallTest
+public class SmartDialCacheTest extends TestCase {
+ public void testIsCountryNanp_CaseInsensitive() {
+ assertFalse(SmartDialCache.isCountryNanp(null));
+ assertFalse(SmartDialCache.isCountryNanp("CN"));
+ assertFalse(SmartDialCache.isCountryNanp("HK"));
+ assertFalse(SmartDialCache.isCountryNanp("uk"));
+ assertFalse(SmartDialCache.isCountryNanp("sg"));
+ assertTrue(SmartDialCache.isCountryNanp("US"));
+ assertTrue(SmartDialCache.isCountryNanp("CA"));
+ assertTrue(SmartDialCache.isCountryNanp("AS"));
+ assertTrue(SmartDialCache.isCountryNanp("AI"));
+ assertTrue(SmartDialCache.isCountryNanp("AG"));
+ assertTrue(SmartDialCache.isCountryNanp("BS"));
+ assertTrue(SmartDialCache.isCountryNanp("BB"));
+ assertTrue(SmartDialCache.isCountryNanp("bm"));
+ assertTrue(SmartDialCache.isCountryNanp("vg"));
+ assertTrue(SmartDialCache.isCountryNanp("ky"));
+ assertTrue(SmartDialCache.isCountryNanp("dm"));
+ assertTrue(SmartDialCache.isCountryNanp("do"));
+ assertTrue(SmartDialCache.isCountryNanp("gd"));
+ assertTrue(SmartDialCache.isCountryNanp("gu"));
+ assertTrue(SmartDialCache.isCountryNanp("jm"));
+ assertTrue(SmartDialCache.isCountryNanp("pr"));
+ assertTrue(SmartDialCache.isCountryNanp("ms"));
+ assertTrue(SmartDialCache.isCountryNanp("mp"));
+ assertTrue(SmartDialCache.isCountryNanp("kn"));
+ assertTrue(SmartDialCache.isCountryNanp("lc"));
+ assertTrue(SmartDialCache.isCountryNanp("vc"));
+ assertTrue(SmartDialCache.isCountryNanp("tt"));
+ assertTrue(SmartDialCache.isCountryNanp("tc"));
+ assertTrue(SmartDialCache.isCountryNanp("vi"));
+ }
+}
diff --git a/tests/src/com/android/dialer/dialpad/SmartDialNameMatcherTest.java b/tests/src/com/android/dialer/dialpad/SmartDialNameMatcherTest.java
index 08939b48e..3c481d0c7 100644
--- a/tests/src/com/android/dialer/dialpad/SmartDialNameMatcherTest.java
+++ b/tests/src/com/android/dialer/dialpad/SmartDialNameMatcherTest.java
@@ -91,6 +91,8 @@ public class SmartDialNameMatcherTest extends TestCase {
checkMatches("William John-Smith", "5764", true, 8, 9, 13, 16);
// make sure multiple spaces don't mess things up
checkMatches("William John---Smith", "5764", true, 15, 16, 22, 25);
+
+ checkMatches("Berkeley Hair-Studio", "788346", true, 14, 20);
}
public void testMatches_repeatedSeparators() {
@@ -114,6 +116,7 @@ public class SmartDialNameMatcherTest extends TestCase {
public void testMatches_umlaut() {
checkMatches("ÄÖÜäöü", "268268", true, 0, 6);
}
+
// TODO: Great if it was treated as "s" or "ss. Figure out if possible without prefix trie?
@Suppress
public void testMatches_germanSharpS() {
@@ -135,6 +138,80 @@ public class SmartDialNameMatcherTest extends TestCase {
fail("Cyrillic letters aren't supported yet.");
}
+
+ public void testMatches_NumberBasic() {
+ // Simple basic examples that start the match from the start of the number
+ checkMatchesNumber("5103337596", "510", true, 0, 3);
+ checkMatchesNumber("5103337596", "511", false, 0, 0);
+ checkMatchesNumber("5103337596", "5103337596", true, 0, 10);
+ checkMatchesNumber("123-456-789", "123456789", true, 0, 11);
+ checkMatchesNumber("123-456-789", "123456788", false, 0, 0);
+ checkMatchesNumber("09999999999", "099", true, 0, 3);
+ }
+
+ public void testMatches_NumberWithCountryCode() {
+ // These matches should ignore the country prefix
+ // USA (+1)
+ checkMatchesNumber("+15103337596", "5103337596", true, 2, 12);
+ checkMatchesNumber("+15103337596", "15103337596", true, 0, 12);
+
+ // Singapore (+65)
+ checkMatchesNumber("+6591776930", "6591", true, 0, 5);
+ checkMatchesNumber("+6591776930", "9177", true, 3, 7);
+ checkMatchesNumber("+6591776930", "5917", false, 3, 7);
+
+ // Hungary (+36)
+ checkMatchesNumber("+3612345678", "361234", true, 0, 7);
+ checkMatchesNumber("+3612345678", "1234", true, 3, 7);
+
+ // Hongkong (+852)
+ checkMatchesNumber("+852 2222 2222", "85222222222", true, 0, 14);
+ checkMatchesNumber("+852 2222 3333", "2222", true, 5, 9);
+
+ // Invalid (+854)
+ checkMatchesNumber("+854 1111 2222", "8541111", true, 0, 9);
+ checkMatchesNumber("+854 1111 2222", "1111", false, 0, 0);
+ }
+
+ public void testMatches_NumberNANP() {
+ // An 11 digit number prefixed with 1 should be matched by the 10 digit number, as well as
+ // the 7 digit number (without area code)
+ checkMatchesNumber("1-510-333-7596", "5103337596", true, true, 2, 14);
+ checkMatchesNumber("1-510-333-7596", "3337596", true, true, 6, 14);
+
+ // Invalid NANP numbers should not be matched
+ checkMatchesNumber("1-510-333-759", "510333759", false, true, 0, 0);
+ checkMatchesNumber("510-333-759", "333759", false, true, 0, 0);
+
+ // match should fail if NANP flag is switched off
+ checkMatchesNumber("1-510-333-7596", "3337596", false, false, 0, 0);
+
+ // A 10 digit number without a 1 prefix should be matched by the 7 digit number
+ checkMatchesNumber("(650) 292 2323", "2922323", true, true, 6, 14);
+ checkMatchesNumber("(650) 292 2323", "6502922323", true, true, 0, 14);
+ // match should fail if NANP flag is switched off
+ checkMatchesNumber("(650) 292 2323", "2922323", false, false, 0, 0);
+ // But this should still match (since it is the full number)
+ checkMatchesNumber("(650) 292 2323", "6502922323", true, false, 0, 14);
+ }
+
+
+ private void checkMatchesNumber(String number, String query, boolean expectedMatches,
+ int matchStart, int matchEnd) {
+ checkMatchesNumber(number, query, expectedMatches, false, matchStart, matchEnd);
+ }
+
+ private void checkMatchesNumber(String number, String query, boolean expectedMatches,
+ boolean matchNanp, int matchStart, int matchEnd) {
+ final SmartDialMatchPosition pos = SmartDialNameMatcher.matchesNumber(number, query,
+ matchNanp);
+ assertEquals(expectedMatches, pos != null);
+ if (expectedMatches) {
+ assertEquals("start", matchStart, pos.start);
+ assertEquals("end", matchEnd, pos.end);
+ }
+ }
+
private void checkMatches(String displayName, String query, boolean expectedMatches,
int... expectedMatchPositions) {
final SmartDialNameMatcher matcher = new SmartDialNameMatcher(query);
diff --git a/tests/src/com/android/dialer/dialpad/SmartDialTrieTest.java b/tests/src/com/android/dialer/dialpad/SmartDialTrieTest.java
index 0378555d0..7f55263e1 100644
--- a/tests/src/com/android/dialer/dialpad/SmartDialTrieTest.java
+++ b/tests/src/com/android/dialer/dialpad/SmartDialTrieTest.java
@@ -48,11 +48,11 @@ public class SmartDialTrieTest extends TestCase{
final ContactNumber jasonsmitt = new ContactNumber(1, "Jason Smitt", "0", "1", 2);
trie.put(jasonsmith);
trie.put(jasonsmitt);
- assertEquals(true, trie.getAllWithPrefix("5276676484").contains(jasonsmith));
- assertEquals(false, trie.getAllWithPrefix("5276676484").contains(jasonsmitt));
+ assertTrue(trie.getAllWithPrefix("5276676484").contains(jasonsmith));
+ assertFalse(trie.getAllWithPrefix("5276676484").contains(jasonsmitt));
- assertEquals(false, trie.getAllWithPrefix("5276676488").contains(jasonsmith));
- assertEquals(true, trie.getAllWithPrefix("5276676488").contains(jasonsmitt));
+ assertFalse(trie.getAllWithPrefix("5276676488").contains(jasonsmith));
+ assertTrue(trie.getAllWithPrefix("5276676488").contains(jasonsmitt));
}
@@ -66,18 +66,18 @@ public class SmartDialTrieTest extends TestCase{
trie.put(jasonsmitt);
// 6279 corresponds to mary = "Mary Jane" but not "Jason Smitt"
- assertEquals(true, checkContains(trie, maryjane, "6279"));
- assertEquals(false, checkContains(trie, jasonsmitt, "6279"));
+ assertTrue(checkContains(trie, maryjane, "6279"));
+ assertFalse(checkContains(trie, jasonsmitt, "6279"));
// 72 corresponds to sa = "Sarah Smith" but not "Jason Smitt" or "Mary Jane"
- assertEquals(false, checkContains(trie, maryjane, "72"));
- assertEquals(true, checkContains(trie, sarahsmith, "72"));
- assertEquals(false, checkContains(trie, jasonsmitt, "72"));
+ assertFalse(checkContains(trie, maryjane, "72"));
+ assertTrue(checkContains(trie, sarahsmith, "72"));
+ assertFalse(checkContains(trie, jasonsmitt, "72"));
// 76 corresponds to sm = "Sarah Smith" and "Jason Smitt" but not "Mary Jane"
- assertEquals(false, checkContains(trie, maryjane, "76"));
- assertEquals(true, checkContains(trie, sarahsmith, "76"));
- assertEquals(true, checkContains(trie, jasonsmitt, "76"));
+ assertFalse(checkContains(trie, maryjane, "76"));
+ assertTrue(checkContains(trie, sarahsmith, "76"));
+ assertTrue(checkContains(trie, jasonsmitt, "76"));
}
public void testPutForNameTokens() {
@@ -86,11 +86,11 @@ public class SmartDialTrieTest extends TestCase{
trie.put(jasonfwilliams);
// 527 corresponds to jas = "Jason"
- assertEquals(true, checkContains(trie, jasonfwilliams, "527"));
+ assertTrue(checkContains(trie, jasonfwilliams, "527"));
// 945 corresponds to wil = "Wil"
- assertEquals(true, checkContains(trie, jasonfwilliams, "945"));
+ assertTrue(checkContains(trie, jasonfwilliams, "945"));
// 66 doesn't match
- assertEquals(false, checkContains(trie, jasonfwilliams, "66"));
+ assertFalse(checkContains(trie, jasonfwilliams, "66"));
}
public void testPutForInitialMatches() {
@@ -99,21 +99,21 @@ public class SmartDialTrieTest extends TestCase{
new ContactNumber(0, "Martin Jr Harry", "0", "0", 1);
trie.put(martinjuniorharry);
// 654 corresponds to mjh = "(M)artin (J)r (H)arry"
- assertEquals(true, checkContains(trie, martinjuniorharry, "654"));
+ assertTrue(checkContains(trie, martinjuniorharry, "654"));
// The reverse (456) does not match (for now)
- assertEquals(false, checkContains(trie, martinjuniorharry, "456"));
+ assertFalse(checkContains(trie, martinjuniorharry, "456"));
// 6542 corresponds to mjha = "(M)artin (J)r (Ha)rry"
- assertEquals(true, checkContains(trie, martinjuniorharry, "6542"));
+ assertTrue(checkContains(trie, martinjuniorharry, "6542"));
// 542 corresponds to jha = "Martin (J)r (Ha)rry"
- assertEquals(true, checkContains(trie, martinjuniorharry, "542"));
+ assertTrue(checkContains(trie, martinjuniorharry, "542"));
// 547 doesn't match
- assertEquals(false, checkContains(trie, martinjuniorharry, "547"));
+ assertFalse(checkContains(trie, martinjuniorharry, "547"));
// 655 doesn't match
- assertEquals(false, checkContains(trie, martinjuniorharry, "655"));
+ assertFalse(checkContains(trie, martinjuniorharry, "655"));
// 653 doesn't match
- assertEquals(false, checkContains(trie, martinjuniorharry, "653"));
+ assertFalse(checkContains(trie, martinjuniorharry, "653"));
// 6543 doesn't match
- assertEquals(false, checkContains(trie, martinjuniorharry, "6543"));
+ assertFalse(checkContains(trie, martinjuniorharry, "6543"));
}
public void testSeparators() {
@@ -124,9 +124,9 @@ public class SmartDialTrieTest extends TestCase{
for (int i = 0; i < name.length(); i++) {
// separators at position 8 and 12
if (i == 8 || i == 14) {
- assertEquals(true, bytes[i] == -1);
+ assertTrue(bytes[i] == -1);
} else {
- assertEquals(false, bytes[i] == -1);
+ assertFalse(bytes[i] == -1);
}
}
}
@@ -137,8 +137,8 @@ public class SmartDialTrieTest extends TestCase{
final ContactNumber bronte = new ContactNumber(2, "Brontë", "0", "1", 2);
trie.put(reenee);
trie.put(bronte);
- assertEquals(true, checkContains(trie, reenee, "733633"));
- assertEquals(true, checkContains(trie, bronte, "276683"));
+ assertTrue(checkContains(trie, reenee, "733633"));
+ assertTrue(checkContains(trie, bronte, "276683"));
}
public void testPutForNumbers() {
@@ -150,15 +150,111 @@ public class SmartDialTrieTest extends TestCase{
final ContactNumber contactno3 = new ContactNumber(0, "James", "+13684976334", "0", 1);
trie.put(contactno3);
// all phone numbers belonging to the contact should correspond to it
- assertEquals(true, checkContains(trie, contactno1, "510"));
- assertEquals(false, checkContains(trie, contactno1, "511"));
- assertEquals(true, checkContains(trie, contactno2, "77212862357"));
- assertEquals(false, checkContains(trie, contactno2, "77212862356"));
- assertEquals(true, checkContains(trie, contactno3, "1368"));
- assertEquals(false, checkContains(trie, contactno3, "1367"));
+ assertTrue(checkContains(trie, contactno1, "510"));
+ assertFalse(checkContains(trie, contactno1, "511"));
+ assertTrue(checkContains(trie, contactno2, "77212862357"));
+ assertFalse(checkContains(trie, contactno2, "77212862356"));
+ assertTrue(checkContains(trie, contactno3, "1368"));
+ assertFalse(checkContains(trie, contactno3, "1367"));
}
+ public void testPutNumbersCountryCode() {
+ final SmartDialTrie trie = new SmartDialTrie();
+ final ContactNumber contactno1 = new ContactNumber(0, "James", "+13684976334", "0", 1);
+ trie.put(contactno1);
+
+ // all phone numbers belonging to the contact should correspond to it
+ assertTrue(checkContains(trie, contactno1, "1368"));
+ assertTrue(checkContains(trie, contactno1, "368497"));
+ assertFalse(checkContains(trie, contactno1, "2368497"));
+
+ final ContactNumber contactno2 = new ContactNumber(0, "Jason", "+65 9177-6930", "0", 1);
+ trie.put(contactno2);
+
+ assertTrue(checkContains(trie, contactno2, "6591776930"));
+ assertTrue(checkContains(trie, contactno2, "91776930"));
+ assertFalse(checkContains(trie, contactno2, "591776930"));
+
+ final ContactNumber contactno3 = new ContactNumber(0, "Mike", "+85212345678", "0", 1);
+ trie.put(contactno3);
+ assertTrue(checkContains(trie, contactno3, "85212345678"));
+ assertTrue(checkContains(trie, contactno3, "12345678"));
+ assertFalse(checkContains(trie, contactno2, "5212345678"));
+
+ // Invalid country code, don't try to parse it
+ final ContactNumber contactno4 = new ContactNumber(0, "Invalid", "+85112345678", "0", 1);
+ trie.put(contactno4);
+ assertTrue(checkContains(trie, contactno4, "85112345678"));
+ assertFalse(checkContains(trie, contactno4, "12345678"));
+
+ final ContactNumber contactno5 = new ContactNumber(0, "Invalid", "+852", "0", 1);
+ // Shouldn't crash
+ trie.put(contactno5);
+ }
+
+ // Tests special case handling for NANP numbers
+ public void testPutNumbersNANP() {
+
+ final SmartDialTrie trie = new SmartDialTrie(true /* formatNanp */);
+ // Unformatted number with 1 prefix
+ final ContactNumber contactno1 = new ContactNumber(0, "James", "16503337596", "0", 1);
+ trie.put(contactno1);
+
+ assertTrue(checkContains(trie, contactno1, "16503337596"));
+ assertTrue(checkContains(trie, contactno1, "6503337596"));
+ assertTrue(checkContains(trie, contactno1, "3337596"));
+
+ // Number with seperators
+ final ContactNumber contactno2 = new ContactNumber(0, "Michael", "5109921234", "0", 1);
+ trie.put(contactno2);
+ assertTrue(checkContains(trie, contactno2, "5109921234"));
+ assertTrue(checkContains(trie, contactno2, "9921234"));
+
+ // Number with area code only + separators
+ final ContactNumber contactno3 = new ContactNumber(0, "Jason", "(415)-123-4567", "0", 1);
+ trie.put(contactno3);
+ assertTrue(checkContains(trie, contactno3, "4151234567"));
+ assertTrue(checkContains(trie, contactno3, "1234567"));
+
+ // Number without +1 prefix but is a NANP number
+ final ContactNumber contactno4 = new ContactNumber(0, "Mike", "1 510-284-9170", "0", 1);
+ trie.put(contactno4);
+ assertTrue(checkContains(trie, contactno4, "15102849170"));
+ assertTrue(checkContains(trie, contactno4, "5102849170"));
+ assertTrue(checkContains(trie, contactno4, "2849170"));
+
+ // Invalid number(has 1 prefix, but is only 10 characters long)
+ final ContactNumber contactno5 = new ContactNumber(0, "Invalid", "1-415-123-123", "0", 1);
+ trie.put(contactno5);
+ // It should still be inserted as is
+ assertTrue(checkContains(trie, contactno5, "1415123123"));
+ // But the NANP special case handling should not work
+ assertFalse(checkContains(trie, contactno5, "415123123"));
+ assertFalse(checkContains(trie, contactno5, "123123"));
+
+ // Invalid number(only 9 characters long)
+ final ContactNumber contactno6 = new ContactNumber(0, "Invalid2", "415-123-123", "0", 1);
+ trie.put(contactno6);
+ // It should still be inserted as is
+ assertTrue(checkContains(trie, contactno6, "415123123"));
+ // But the NANP special case handling should not work
+ assertFalse(checkContains(trie, contactno6, "123123"));
+
+ // If user's region is determined to be not in North America, then the NANP number
+ // workarounds should not be applied
+ final SmartDialTrie trieNonNANP = new SmartDialTrie();
+
+ trieNonNANP.put(contactno3);
+ assertTrue(checkContains(trieNonNANP, contactno3, "4151234567"));
+ assertFalse(checkContains(trieNonNANP, contactno3, "1234567"));
+
+ trieNonNANP.put(contactno4);
+ assertTrue(checkContains(trieNonNANP, contactno4, "15102849170"));
+ assertFalse(checkContains(trieNonNANP, contactno4, "5102849170"));
+ assertFalse(checkContains(trieNonNANP, contactno4, "2849170"));
+ }
+
public void testNodeConstructor() {
final Node n = new Node();
// Node member variables should not be initialized by default at construction to reduce
@@ -185,8 +281,8 @@ public class SmartDialTrieTest extends TestCase{
final ContactNumber contact = new ContactNumber(0, "James", "510-527-2357", "0", 1);
final ContactNumber contactNotIn = new ContactNumber(2, "Jason Smitt", "0", "2", 3);
n.add(contact);
- assertEquals(true, n.getContents().contains(contact));
- assertEquals(false, n.getContents().contains(contactNotIn));
+ assertTrue(n.getContents().contains(contact));
+ assertFalse(n.getContents().contains(contactNotIn));
}
private boolean checkContains(SmartDialTrie trie, ContactNumber contact, CharSequence prefix) {