Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2013 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 | |
linyuh | bf4bb05 | 2017-12-21 15:42:00 -0800 | [diff] [blame^] | 17 | package com.android.dialer.smartdial.util; |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 18 | |
| 19 | import android.content.Context; |
| 20 | import android.content.SharedPreferences; |
| 21 | import android.preference.PreferenceManager; |
| 22 | import android.support.annotation.VisibleForTesting; |
| 23 | import android.telephony.TelephonyManager; |
| 24 | import android.text.TextUtils; |
linyuh | bf4bb05 | 2017-12-21 15:42:00 -0800 | [diff] [blame^] | 25 | import com.android.dialer.smartdial.map.CompositeSmartDialMap; |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 26 | import java.util.ArrayList; |
| 27 | import java.util.HashSet; |
| 28 | import java.util.Set; |
| 29 | |
| 30 | /** |
| 31 | * Smart Dial utility class to find prefixes of contacts. It contains both methods to find supported |
| 32 | * prefix combinations for contact names, and also methods to find supported prefix combinations for |
| 33 | * contacts' phone numbers. Each contact name is separated into several tokens, such as first name, |
| 34 | * middle name, family name etc. Each phone number is also separated into country code, NANP area |
| 35 | * code, and local number if such separation is possible. |
| 36 | */ |
| 37 | public class SmartDialPrefix { |
| 38 | |
| 39 | /** |
| 40 | * The number of starting and ending tokens in a contact's name considered for initials. For |
| 41 | * example, if both constants are set to 2, and a contact's name is "Albert Ben Charles Daniel Ed |
| 42 | * Foster", the first two tokens "Albert" "Ben", and last two tokens "Ed" "Foster" can be replaced |
| 43 | * by their initials in contact name matching. Users can look up this contact by combinations of |
| 44 | * his initials such as "AF" "BF" "EF" "ABF" "BEF" "ABEF" etc, but can not use combinations such |
| 45 | * as "CF" "DF" "ACF" "ADF" etc. |
| 46 | */ |
| 47 | private static final int LAST_TOKENS_FOR_INITIALS = 2; |
| 48 | |
| 49 | private static final int FIRST_TOKENS_FOR_INITIALS = 2; |
| 50 | |
| 51 | /** The country code of the user's sim card obtained by calling getSimCountryIso */ |
| 52 | private static final String PREF_USER_SIM_COUNTRY_CODE = |
| 53 | "DialtactsActivity_user_sim_country_code"; |
| 54 | |
| 55 | private static final String PREF_USER_SIM_COUNTRY_CODE_DEFAULT = null; |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 56 | |
| 57 | private static String sUserSimCountryCode = PREF_USER_SIM_COUNTRY_CODE_DEFAULT; |
| 58 | /** Indicates whether user is in NANP regions. */ |
| 59 | private static boolean sUserInNanpRegion = false; |
| 60 | /** Set of country names that use NANP code. */ |
| 61 | private static Set<String> sNanpCountries = null; |
| 62 | /** Set of supported country codes in front of the phone number. */ |
| 63 | private static Set<String> sCountryCodes = null; |
| 64 | |
| 65 | private static boolean sNanpInitialized = false; |
| 66 | |
| 67 | /** Initializes the Nanp settings, and finds out whether user is in a NANP region. */ |
| 68 | public static void initializeNanpSettings(Context context) { |
| 69 | final TelephonyManager manager = |
| 70 | (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); |
| 71 | if (manager != null) { |
| 72 | sUserSimCountryCode = manager.getSimCountryIso(); |
| 73 | } |
| 74 | |
Eric Erfanian | fc0eb8c | 2017-08-31 06:57:16 -0700 | [diff] [blame] | 75 | final SharedPreferences prefs = |
| 76 | PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 77 | |
| 78 | if (sUserSimCountryCode != null) { |
| 79 | /** Updates shared preferences with the latest country obtained from getSimCountryIso. */ |
| 80 | prefs.edit().putString(PREF_USER_SIM_COUNTRY_CODE, sUserSimCountryCode).apply(); |
| 81 | } else { |
| 82 | /** Uses previously stored country code if loading fails. */ |
| 83 | sUserSimCountryCode = |
| 84 | prefs.getString(PREF_USER_SIM_COUNTRY_CODE, PREF_USER_SIM_COUNTRY_CODE_DEFAULT); |
| 85 | } |
| 86 | /** Queries the NANP country list to find out whether user is in a NANP region. */ |
| 87 | sUserInNanpRegion = isCountryNanp(sUserSimCountryCode); |
| 88 | sNanpInitialized = true; |
| 89 | } |
| 90 | |
| 91 | /** |
| 92 | * Parses a contact's name into a list of separated tokens. |
| 93 | * |
| 94 | * @param contactName Contact's name stored in string. |
| 95 | * @return A list of name tokens, for example separated first names, last name, etc. |
| 96 | */ |
linyuh | ab14653 | 2017-12-19 11:28:51 -0800 | [diff] [blame] | 97 | public static ArrayList<String> parseToIndexTokens(Context context, String contactName) { |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 98 | final int length = contactName.length(); |
| 99 | final ArrayList<String> result = new ArrayList<>(); |
| 100 | char c; |
| 101 | final StringBuilder currentIndexToken = new StringBuilder(); |
| 102 | /** |
| 103 | * Iterates through the whole name string. If the current character is a valid character, append |
| 104 | * it to the current token. If the current character is not a valid character, for example space |
| 105 | * " ", mark the current token as complete and add it to the list of tokens. |
| 106 | */ |
| 107 | for (int i = 0; i < length; i++) { |
linyuh | ab14653 | 2017-12-19 11:28:51 -0800 | [diff] [blame] | 108 | c = CompositeSmartDialMap.normalizeCharacter(context, contactName.charAt(i)); |
| 109 | if (CompositeSmartDialMap.isValidDialpadCharacter(context, c)) { |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 110 | /** Converts a character into the number on dialpad that represents the character. */ |
linyuh | ab14653 | 2017-12-19 11:28:51 -0800 | [diff] [blame] | 111 | currentIndexToken.append(CompositeSmartDialMap.getDialpadIndex(context, c)); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 112 | } else { |
| 113 | if (currentIndexToken.length() != 0) { |
| 114 | result.add(currentIndexToken.toString()); |
| 115 | } |
| 116 | currentIndexToken.delete(0, currentIndexToken.length()); |
| 117 | } |
| 118 | } |
| 119 | |
| 120 | /** Adds the last token in case it has not been added. */ |
| 121 | if (currentIndexToken.length() != 0) { |
| 122 | result.add(currentIndexToken.toString()); |
| 123 | } |
| 124 | return result; |
| 125 | } |
| 126 | |
| 127 | /** |
| 128 | * Generates a list of strings that any prefix of any string in the list can be used to look up |
| 129 | * the contact's name. |
| 130 | * |
| 131 | * @param index The contact's name in string. |
| 132 | * @return A List of strings, whose prefix can be used to look up the contact. |
| 133 | */ |
linyuh | ab14653 | 2017-12-19 11:28:51 -0800 | [diff] [blame] | 134 | public static ArrayList<String> generateNamePrefixes(Context context, String index) { |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 135 | final ArrayList<String> result = new ArrayList<>(); |
| 136 | |
| 137 | /** Parses the name into a list of tokens. */ |
linyuh | ab14653 | 2017-12-19 11:28:51 -0800 | [diff] [blame] | 138 | final ArrayList<String> indexTokens = parseToIndexTokens(context, index); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 139 | |
| 140 | if (indexTokens.size() > 0) { |
| 141 | /** |
| 142 | * Adds the full token combinations to the list. For example, a contact with name "Albert Ben |
| 143 | * Ed Foster" can be looked up by any prefix of the following strings "Foster" "EdFoster" |
| 144 | * "BenEdFoster" and "AlbertBenEdFoster". This covers all cases of look up that contains only |
| 145 | * one token, and that spans multiple continuous tokens. |
| 146 | */ |
| 147 | final StringBuilder fullNameToken = new StringBuilder(); |
| 148 | for (int i = indexTokens.size() - 1; i >= 0; i--) { |
| 149 | fullNameToken.insert(0, indexTokens.get(i)); |
| 150 | result.add(fullNameToken.toString()); |
| 151 | } |
| 152 | |
| 153 | /** |
| 154 | * Adds initial combinations to the list, with the number of initials restricted by {@link |
| 155 | * #LAST_TOKENS_FOR_INITIALS} and {@link #FIRST_TOKENS_FOR_INITIALS}. For example, a contact |
| 156 | * with name "Albert Ben Ed Foster" can be looked up by any prefix of the following strings |
| 157 | * "EFoster" "BFoster" "BEFoster" "AFoster" "ABFoster" "AEFoster" and "ABEFoster". This covers |
| 158 | * all cases of initial lookup. |
| 159 | */ |
| 160 | ArrayList<String> fullNames = new ArrayList<>(); |
| 161 | fullNames.add(indexTokens.get(indexTokens.size() - 1)); |
| 162 | final int recursiveNameStart = result.size(); |
| 163 | int recursiveNameEnd = result.size(); |
| 164 | String initial = ""; |
| 165 | for (int i = indexTokens.size() - 2; i >= 0; i--) { |
| 166 | if ((i >= indexTokens.size() - LAST_TOKENS_FOR_INITIALS) |
| 167 | || (i < FIRST_TOKENS_FOR_INITIALS)) { |
| 168 | initial = indexTokens.get(i).substring(0, 1); |
| 169 | |
| 170 | /** Recursively adds initial combinations to the list. */ |
| 171 | for (int j = 0; j < fullNames.size(); ++j) { |
| 172 | result.add(initial + fullNames.get(j)); |
| 173 | } |
| 174 | for (int j = recursiveNameStart; j < recursiveNameEnd; ++j) { |
| 175 | result.add(initial + result.get(j)); |
| 176 | } |
| 177 | recursiveNameEnd = result.size(); |
| 178 | final String currentFullName = fullNames.get(fullNames.size() - 1); |
| 179 | fullNames.add(indexTokens.get(i) + currentFullName); |
| 180 | } |
| 181 | } |
| 182 | } |
| 183 | |
| 184 | return result; |
| 185 | } |
| 186 | |
| 187 | /** |
| 188 | * Computes a list of number strings based on tokens of a given phone number. Any prefix of any |
| 189 | * string in the list can be used to look up the phone number. The list include the full phone |
| 190 | * number, the national number if there is a country code in the phone number, and the local |
| 191 | * number if there is an area code in the phone number following the NANP format. For example, if |
| 192 | * a user has phone number +41 71 394 8392, the list will contain 41713948392 and 713948392. Any |
| 193 | * prefix to either of the strings can be used to look up the phone number. If a user has a phone |
| 194 | * number +1 555-302-3029 (NANP format), the list will contain 15553023029, 5553023029, and |
| 195 | * 3023029. |
| 196 | * |
| 197 | * @param number String of user's phone number. |
| 198 | * @return A list of strings where any prefix of any entry can be used to look up the number. |
| 199 | */ |
linyuh | ab14653 | 2017-12-19 11:28:51 -0800 | [diff] [blame] | 200 | public static ArrayList<String> parseToNumberTokens(Context context, String number) { |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 201 | final ArrayList<String> result = new ArrayList<>(); |
| 202 | if (!TextUtils.isEmpty(number)) { |
| 203 | /** Adds the full number to the list. */ |
linyuh | ab14653 | 2017-12-19 11:28:51 -0800 | [diff] [blame] | 204 | result.add(SmartDialNameMatcher.normalizeNumber(context, number)); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 205 | |
linyuh | ab14653 | 2017-12-19 11:28:51 -0800 | [diff] [blame] | 206 | final PhoneNumberTokens phoneNumberTokens = parsePhoneNumber(context, number); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 207 | if (phoneNumberTokens == null) { |
| 208 | return result; |
| 209 | } |
| 210 | |
| 211 | if (phoneNumberTokens.countryCodeOffset != 0) { |
| 212 | result.add( |
| 213 | SmartDialNameMatcher.normalizeNumber( |
linyuh | ab14653 | 2017-12-19 11:28:51 -0800 | [diff] [blame] | 214 | context, number, phoneNumberTokens.countryCodeOffset)); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 215 | } |
| 216 | |
| 217 | if (phoneNumberTokens.nanpCodeOffset != 0) { |
| 218 | result.add( |
linyuh | ab14653 | 2017-12-19 11:28:51 -0800 | [diff] [blame] | 219 | SmartDialNameMatcher.normalizeNumber( |
| 220 | context, number, phoneNumberTokens.nanpCodeOffset)); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 221 | } |
| 222 | } |
| 223 | return result; |
| 224 | } |
| 225 | |
| 226 | /** |
| 227 | * Parses a phone number to find out whether it has country code and NANP area code. |
| 228 | * |
| 229 | * @param number Raw phone number. |
| 230 | * @return a PhoneNumberToken instance with country code, NANP code information. |
| 231 | */ |
linyuh | ab14653 | 2017-12-19 11:28:51 -0800 | [diff] [blame] | 232 | public static PhoneNumberTokens parsePhoneNumber(Context context, String number) { |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 233 | String countryCode = ""; |
| 234 | int countryCodeOffset = 0; |
| 235 | int nanpNumberOffset = 0; |
| 236 | |
| 237 | if (!TextUtils.isEmpty(number)) { |
linyuh | ab14653 | 2017-12-19 11:28:51 -0800 | [diff] [blame] | 238 | String normalizedNumber = SmartDialNameMatcher.normalizeNumber(context, number); |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 239 | if (number.charAt(0) == '+') { |
| 240 | /** If the number starts with '+', tries to find valid country code. */ |
| 241 | for (int i = 1; i <= 1 + 3; i++) { |
| 242 | if (number.length() <= i) { |
| 243 | break; |
| 244 | } |
| 245 | countryCode = number.substring(1, i); |
| 246 | if (isValidCountryCode(countryCode)) { |
| 247 | countryCodeOffset = i; |
| 248 | break; |
| 249 | } |
| 250 | } |
| 251 | } else { |
| 252 | /** |
| 253 | * If the number does not start with '+', finds out whether it is in NANP format and has '1' |
| 254 | * preceding the number. |
| 255 | */ |
| 256 | if ((normalizedNumber.length() == 11) |
| 257 | && (normalizedNumber.charAt(0) == '1') |
| 258 | && (sUserInNanpRegion)) { |
| 259 | countryCode = "1"; |
| 260 | countryCodeOffset = number.indexOf(normalizedNumber.charAt(1)); |
| 261 | if (countryCodeOffset == -1) { |
| 262 | countryCodeOffset = 0; |
| 263 | } |
| 264 | } |
| 265 | } |
| 266 | |
| 267 | /** If user is in NANP region, finds out whether a number is in NANP format. */ |
| 268 | if (sUserInNanpRegion) { |
| 269 | String areaCode = ""; |
| 270 | if (countryCode.equals("") && normalizedNumber.length() == 10) { |
| 271 | /** |
| 272 | * if the number has no country code but fits the NANP format, extracts the NANP area |
| 273 | * code, and finds out offset of the local number. |
| 274 | */ |
| 275 | areaCode = normalizedNumber.substring(0, 3); |
| 276 | } else if (countryCode.equals("1") && normalizedNumber.length() == 11) { |
| 277 | /** |
| 278 | * If the number has country code '1', finds out area code and offset of the local number. |
| 279 | */ |
| 280 | areaCode = normalizedNumber.substring(1, 4); |
| 281 | } |
| 282 | if (!areaCode.equals("")) { |
| 283 | final int areaCodeIndex = number.indexOf(areaCode); |
| 284 | if (areaCodeIndex != -1) { |
| 285 | nanpNumberOffset = number.indexOf(areaCode) + 3; |
| 286 | } |
| 287 | } |
| 288 | } |
| 289 | } |
| 290 | return new PhoneNumberTokens(countryCode, countryCodeOffset, nanpNumberOffset); |
| 291 | } |
| 292 | |
| 293 | /** Checkes whether a country code is valid. */ |
| 294 | private static boolean isValidCountryCode(String countryCode) { |
| 295 | if (sCountryCodes == null) { |
| 296 | sCountryCodes = initCountryCodes(); |
| 297 | } |
| 298 | return sCountryCodes.contains(countryCode); |
| 299 | } |
| 300 | |
| 301 | private static Set<String> initCountryCodes() { |
| 302 | final HashSet<String> result = new HashSet<String>(); |
| 303 | result.add("1"); |
| 304 | result.add("7"); |
| 305 | result.add("20"); |
| 306 | result.add("27"); |
| 307 | result.add("30"); |
| 308 | result.add("31"); |
| 309 | result.add("32"); |
| 310 | result.add("33"); |
| 311 | result.add("34"); |
| 312 | result.add("36"); |
| 313 | result.add("39"); |
| 314 | result.add("40"); |
| 315 | result.add("41"); |
| 316 | result.add("43"); |
| 317 | result.add("44"); |
| 318 | result.add("45"); |
| 319 | result.add("46"); |
| 320 | result.add("47"); |
| 321 | result.add("48"); |
| 322 | result.add("49"); |
| 323 | result.add("51"); |
| 324 | result.add("52"); |
| 325 | result.add("53"); |
| 326 | result.add("54"); |
| 327 | result.add("55"); |
| 328 | result.add("56"); |
| 329 | result.add("57"); |
| 330 | result.add("58"); |
| 331 | result.add("60"); |
| 332 | result.add("61"); |
| 333 | result.add("62"); |
| 334 | result.add("63"); |
| 335 | result.add("64"); |
| 336 | result.add("65"); |
| 337 | result.add("66"); |
| 338 | result.add("81"); |
| 339 | result.add("82"); |
| 340 | result.add("84"); |
| 341 | result.add("86"); |
| 342 | result.add("90"); |
| 343 | result.add("91"); |
| 344 | result.add("92"); |
| 345 | result.add("93"); |
| 346 | result.add("94"); |
| 347 | result.add("95"); |
| 348 | result.add("98"); |
| 349 | result.add("211"); |
| 350 | result.add("212"); |
| 351 | result.add("213"); |
| 352 | result.add("216"); |
| 353 | result.add("218"); |
| 354 | result.add("220"); |
| 355 | result.add("221"); |
| 356 | result.add("222"); |
| 357 | result.add("223"); |
| 358 | result.add("224"); |
| 359 | result.add("225"); |
| 360 | result.add("226"); |
| 361 | result.add("227"); |
| 362 | result.add("228"); |
| 363 | result.add("229"); |
| 364 | result.add("230"); |
| 365 | result.add("231"); |
| 366 | result.add("232"); |
| 367 | result.add("233"); |
| 368 | result.add("234"); |
| 369 | result.add("235"); |
| 370 | result.add("236"); |
| 371 | result.add("237"); |
| 372 | result.add("238"); |
| 373 | result.add("239"); |
| 374 | result.add("240"); |
| 375 | result.add("241"); |
| 376 | result.add("242"); |
| 377 | result.add("243"); |
| 378 | result.add("244"); |
| 379 | result.add("245"); |
| 380 | result.add("246"); |
| 381 | result.add("247"); |
| 382 | result.add("248"); |
| 383 | result.add("249"); |
| 384 | result.add("250"); |
| 385 | result.add("251"); |
| 386 | result.add("252"); |
| 387 | result.add("253"); |
| 388 | result.add("254"); |
| 389 | result.add("255"); |
| 390 | result.add("256"); |
| 391 | result.add("257"); |
| 392 | result.add("258"); |
| 393 | result.add("260"); |
| 394 | result.add("261"); |
| 395 | result.add("262"); |
| 396 | result.add("263"); |
| 397 | result.add("264"); |
| 398 | result.add("265"); |
| 399 | result.add("266"); |
| 400 | result.add("267"); |
| 401 | result.add("268"); |
| 402 | result.add("269"); |
| 403 | result.add("290"); |
| 404 | result.add("291"); |
| 405 | result.add("297"); |
| 406 | result.add("298"); |
| 407 | result.add("299"); |
| 408 | result.add("350"); |
| 409 | result.add("351"); |
| 410 | result.add("352"); |
| 411 | result.add("353"); |
| 412 | result.add("354"); |
| 413 | result.add("355"); |
| 414 | result.add("356"); |
| 415 | result.add("357"); |
| 416 | result.add("358"); |
| 417 | result.add("359"); |
| 418 | result.add("370"); |
| 419 | result.add("371"); |
| 420 | result.add("372"); |
| 421 | result.add("373"); |
| 422 | result.add("374"); |
| 423 | result.add("375"); |
| 424 | result.add("376"); |
| 425 | result.add("377"); |
| 426 | result.add("378"); |
| 427 | result.add("379"); |
| 428 | result.add("380"); |
| 429 | result.add("381"); |
| 430 | result.add("382"); |
| 431 | result.add("385"); |
| 432 | result.add("386"); |
| 433 | result.add("387"); |
| 434 | result.add("389"); |
| 435 | result.add("420"); |
| 436 | result.add("421"); |
| 437 | result.add("423"); |
| 438 | result.add("500"); |
| 439 | result.add("501"); |
| 440 | result.add("502"); |
| 441 | result.add("503"); |
| 442 | result.add("504"); |
| 443 | result.add("505"); |
| 444 | result.add("506"); |
| 445 | result.add("507"); |
| 446 | result.add("508"); |
| 447 | result.add("509"); |
| 448 | result.add("590"); |
| 449 | result.add("591"); |
| 450 | result.add("592"); |
| 451 | result.add("593"); |
| 452 | result.add("594"); |
| 453 | result.add("595"); |
| 454 | result.add("596"); |
| 455 | result.add("597"); |
| 456 | result.add("598"); |
| 457 | result.add("599"); |
| 458 | result.add("670"); |
| 459 | result.add("672"); |
| 460 | result.add("673"); |
| 461 | result.add("674"); |
| 462 | result.add("675"); |
| 463 | result.add("676"); |
| 464 | result.add("677"); |
| 465 | result.add("678"); |
| 466 | result.add("679"); |
| 467 | result.add("680"); |
| 468 | result.add("681"); |
| 469 | result.add("682"); |
| 470 | result.add("683"); |
| 471 | result.add("685"); |
| 472 | result.add("686"); |
| 473 | result.add("687"); |
| 474 | result.add("688"); |
| 475 | result.add("689"); |
| 476 | result.add("690"); |
| 477 | result.add("691"); |
| 478 | result.add("692"); |
| 479 | result.add("800"); |
| 480 | result.add("808"); |
| 481 | result.add("850"); |
| 482 | result.add("852"); |
| 483 | result.add("853"); |
| 484 | result.add("855"); |
| 485 | result.add("856"); |
| 486 | result.add("870"); |
| 487 | result.add("878"); |
| 488 | result.add("880"); |
| 489 | result.add("881"); |
| 490 | result.add("882"); |
| 491 | result.add("883"); |
| 492 | result.add("886"); |
| 493 | result.add("888"); |
| 494 | result.add("960"); |
| 495 | result.add("961"); |
| 496 | result.add("962"); |
| 497 | result.add("963"); |
| 498 | result.add("964"); |
| 499 | result.add("965"); |
| 500 | result.add("966"); |
| 501 | result.add("967"); |
| 502 | result.add("968"); |
| 503 | result.add("970"); |
| 504 | result.add("971"); |
| 505 | result.add("972"); |
| 506 | result.add("973"); |
| 507 | result.add("974"); |
| 508 | result.add("975"); |
| 509 | result.add("976"); |
| 510 | result.add("977"); |
| 511 | result.add("979"); |
| 512 | result.add("992"); |
| 513 | result.add("993"); |
| 514 | result.add("994"); |
| 515 | result.add("995"); |
| 516 | result.add("996"); |
| 517 | result.add("998"); |
| 518 | return result; |
| 519 | } |
| 520 | |
Eric Erfanian | ccca315 | 2017-02-22 16:32:36 -0800 | [diff] [blame] | 521 | /** |
| 522 | * Indicates whether the given country uses NANP numbers |
| 523 | * |
| 524 | * @param country ISO 3166 country code (case doesn't matter) |
| 525 | * @return True if country uses NANP numbers (e.g. US, Canada), false otherwise |
| 526 | * @see <a href="https://en.wikipedia.org/wiki/North_American_Numbering_Plan"> |
| 527 | * https://en.wikipedia.org/wiki/North_American_Numbering_Plan</a> |
| 528 | */ |
| 529 | @VisibleForTesting |
| 530 | public static boolean isCountryNanp(String country) { |
| 531 | if (TextUtils.isEmpty(country)) { |
| 532 | return false; |
| 533 | } |
| 534 | if (sNanpCountries == null) { |
| 535 | sNanpCountries = initNanpCountries(); |
| 536 | } |
| 537 | return sNanpCountries.contains(country.toUpperCase()); |
| 538 | } |
| 539 | |
| 540 | private static Set<String> initNanpCountries() { |
| 541 | final HashSet<String> result = new HashSet<String>(); |
| 542 | result.add("US"); // United States |
| 543 | result.add("CA"); // Canada |
| 544 | result.add("AS"); // American Samoa |
| 545 | result.add("AI"); // Anguilla |
| 546 | result.add("AG"); // Antigua and Barbuda |
| 547 | result.add("BS"); // Bahamas |
| 548 | result.add("BB"); // Barbados |
| 549 | result.add("BM"); // Bermuda |
| 550 | result.add("VG"); // British Virgin Islands |
| 551 | result.add("KY"); // Cayman Islands |
| 552 | result.add("DM"); // Dominica |
| 553 | result.add("DO"); // Dominican Republic |
| 554 | result.add("GD"); // Grenada |
| 555 | result.add("GU"); // Guam |
| 556 | result.add("JM"); // Jamaica |
| 557 | result.add("PR"); // Puerto Rico |
| 558 | result.add("MS"); // Montserrat |
| 559 | result.add("MP"); // Northern Mariana Islands |
| 560 | result.add("KN"); // Saint Kitts and Nevis |
| 561 | result.add("LC"); // Saint Lucia |
| 562 | result.add("VC"); // Saint Vincent and the Grenadines |
| 563 | result.add("TT"); // Trinidad and Tobago |
| 564 | result.add("TC"); // Turks and Caicos Islands |
| 565 | result.add("VI"); // U.S. Virgin Islands |
| 566 | return result; |
| 567 | } |
| 568 | |
| 569 | /** |
| 570 | * Returns whether the user is in a region that uses Nanp format based on the sim location. |
| 571 | * |
| 572 | * @return Whether user is in Nanp region. |
| 573 | */ |
| 574 | public static boolean getUserInNanpRegion() { |
| 575 | return sUserInNanpRegion; |
| 576 | } |
| 577 | |
| 578 | /** Explicitly setting the user Nanp to the given boolean */ |
| 579 | @VisibleForTesting |
| 580 | public static void setUserInNanpRegion(boolean userInNanpRegion) { |
| 581 | sUserInNanpRegion = userInNanpRegion; |
| 582 | } |
| 583 | |
| 584 | /** Class to record phone number parsing information. */ |
| 585 | public static class PhoneNumberTokens { |
| 586 | |
| 587 | /** Country code of the phone number. */ |
| 588 | final String countryCode; |
| 589 | |
| 590 | /** Offset of national number after the country code. */ |
| 591 | final int countryCodeOffset; |
| 592 | |
| 593 | /** Offset of local number after NANP area code. */ |
| 594 | final int nanpCodeOffset; |
| 595 | |
| 596 | public PhoneNumberTokens(String countryCode, int countryCodeOffset, int nanpCodeOffset) { |
| 597 | this.countryCode = countryCode; |
| 598 | this.countryCodeOffset = countryCodeOffset; |
| 599 | this.nanpCodeOffset = nanpCodeOffset; |
| 600 | } |
| 601 | } |
| 602 | } |