summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-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
4 files changed, 517 insertions, 20 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;
+ }
}