1 /* 2 * Copyright (C) 2013 The Libphonenumber Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.i18n.phonenumbers; 18 19 import com.google.i18n.phonenumbers.internal.MatcherApi; 20 import com.google.i18n.phonenumbers.internal.RegexBasedMatcher; 21 import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata; 22 import com.google.i18n.phonenumbers.Phonemetadata.PhoneNumberDesc; 23 import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber; 24 25 import com.google.i18n.phonenumbers.metadata.DefaultMetadataDependenciesProvider; 26 import com.google.i18n.phonenumbers.metadata.source.RegionMetadataSource; 27 import java.util.ArrayList; 28 import java.util.Arrays; 29 import java.util.Collections; 30 import java.util.HashSet; 31 import java.util.List; 32 import java.util.Map; 33 import java.util.Set; 34 import java.util.logging.Level; 35 import java.util.logging.Logger; 36 37 /** 38 * Methods for getting information about short phone numbers, such as short codes and emergency 39 * numbers. Note that most commercial short numbers are not handled here, but by the 40 * {@link PhoneNumberUtil}. 41 * 42 * @author Shaopeng Jia 43 * @author David Yonge-Mallo 44 */ 45 public class ShortNumberInfo { 46 private static final Logger logger = Logger.getLogger(ShortNumberInfo.class.getName()); 47 48 private static final ShortNumberInfo INSTANCE = 49 new ShortNumberInfo( 50 RegexBasedMatcher.create(), 51 DefaultMetadataDependenciesProvider.getInstance().getShortNumberMetadataSource()); 52 53 // In these countries, if extra digits are added to an emergency number, it no longer connects 54 // to the emergency service. 55 private static final Set<String> REGIONS_WHERE_EMERGENCY_NUMBERS_MUST_BE_EXACT = new HashSet<>(); 56 static { 57 REGIONS_WHERE_EMERGENCY_NUMBERS_MUST_BE_EXACT.add("BR"); 58 REGIONS_WHERE_EMERGENCY_NUMBERS_MUST_BE_EXACT.add("CL"); 59 REGIONS_WHERE_EMERGENCY_NUMBERS_MUST_BE_EXACT.add("NI"); 60 } 61 62 /** Cost categories of short numbers. */ 63 public enum ShortNumberCost { 64 TOLL_FREE, 65 STANDARD_RATE, 66 PREMIUM_RATE, 67 UNKNOWN_COST 68 } 69 70 /** Returns the singleton instance of the ShortNumberInfo. */ getInstance()71 public static ShortNumberInfo getInstance() { 72 return INSTANCE; 73 } 74 75 // MatcherApi supports the basic matching method for checking if a given national number matches 76 // a national number pattern defined in the given {@code PhoneNumberDesc}. 77 private final MatcherApi matcherApi; 78 79 // A mapping from a country calling code to the region codes which denote the region represented 80 // by that country calling code. In the case of multiple regions sharing a calling code, such as 81 // the NANPA regions, the one indicated with "isMainCountryForCode" in the metadata should be 82 // first. 83 private final Map<Integer, List<String>> countryCallingCodeToRegionCodeMap; 84 85 private final RegionMetadataSource shortNumberMetadataSource; 86 87 // @VisibleForTesting ShortNumberInfo(MatcherApi matcherApi, RegionMetadataSource shortNumberMetadataSource)88 ShortNumberInfo(MatcherApi matcherApi, 89 RegionMetadataSource shortNumberMetadataSource) { 90 this.matcherApi = matcherApi; 91 this.shortNumberMetadataSource = shortNumberMetadataSource; 92 // TODO: Create ShortNumberInfo for a given map 93 this.countryCallingCodeToRegionCodeMap = 94 CountryCodeToRegionCodeMap.getCountryCodeToRegionCodeMap(); 95 } 96 97 /** 98 * Returns a list with the region codes that match the specific country calling code. For 99 * non-geographical country calling codes, the region code 001 is returned. Also, in the case 100 * of no region code being found, an empty list is returned. 101 */ getRegionCodesForCountryCode(int countryCallingCode)102 private List<String> getRegionCodesForCountryCode(int countryCallingCode) { 103 List<String> regionCodes = countryCallingCodeToRegionCodeMap.get(countryCallingCode); 104 return Collections.unmodifiableList(regionCodes == null ? new ArrayList<String>(0) 105 : regionCodes); 106 } 107 108 /** 109 * Helper method to check that the country calling code of the number matches the region it's 110 * being dialed from. 111 */ regionDialingFromMatchesNumber(PhoneNumber number, String regionDialingFrom)112 private boolean regionDialingFromMatchesNumber(PhoneNumber number, 113 String regionDialingFrom) { 114 List<String> regionCodes = getRegionCodesForCountryCode(number.getCountryCode()); 115 return regionCodes.contains(regionDialingFrom); 116 } 117 118 /** 119 * A thin wrapper around {@code shortNumberMetadataSource} which catches {@link 120 * IllegalArgumentException} for invalid region code and instead returns {@code null} 121 */ getShortNumberMetadataForRegion(String regionCode)122 private PhoneMetadata getShortNumberMetadataForRegion(String regionCode) { 123 if (regionCode == null) { 124 return null; 125 } 126 try { 127 return shortNumberMetadataSource.getMetadataForRegion(regionCode); 128 } catch (IllegalArgumentException e) { 129 return null; 130 } 131 } 132 133 /** 134 * Check whether a short number is a possible number when dialed from the given region. This 135 * provides a more lenient check than {@link #isValidShortNumberForRegion}. 136 * 137 * @param number the short number to check 138 * @param regionDialingFrom the region from which the number is dialed 139 * @return whether the number is a possible short number 140 */ isPossibleShortNumberForRegion(PhoneNumber number, String regionDialingFrom)141 public boolean isPossibleShortNumberForRegion(PhoneNumber number, String regionDialingFrom) { 142 if (!regionDialingFromMatchesNumber(number, regionDialingFrom)) { 143 return false; 144 } 145 PhoneMetadata phoneMetadata = getShortNumberMetadataForRegion(regionDialingFrom); 146 if (phoneMetadata == null) { 147 return false; 148 } 149 int numberLength = getNationalSignificantNumber(number).length(); 150 return phoneMetadata.getGeneralDesc().getPossibleLengthList().contains(numberLength); 151 } 152 153 /** 154 * Check whether a short number is a possible number. If a country calling code is shared by 155 * multiple regions, this returns true if it's possible in any of them. This provides a more 156 * lenient check than {@link #isValidShortNumber}. See {@link 157 * #isPossibleShortNumberForRegion(PhoneNumber, String)} for details. 158 * 159 * @param number the short number to check 160 * @return whether the number is a possible short number 161 */ isPossibleShortNumber(PhoneNumber number)162 public boolean isPossibleShortNumber(PhoneNumber number) { 163 List<String> regionCodes = getRegionCodesForCountryCode(number.getCountryCode()); 164 int shortNumberLength = getNationalSignificantNumber(number).length(); 165 for (String region : regionCodes) { 166 PhoneMetadata phoneMetadata = getShortNumberMetadataForRegion(region); 167 if (phoneMetadata == null) { 168 continue; 169 } 170 if (phoneMetadata.getGeneralDesc().getPossibleLengthList().contains(shortNumberLength)) { 171 return true; 172 } 173 } 174 return false; 175 } 176 177 /** 178 * Tests whether a short number matches a valid pattern in a region. Note that this doesn't verify 179 * the number is actually in use, which is impossible to tell by just looking at the number 180 * itself. 181 * 182 * @param number the short number for which we want to test the validity 183 * @param regionDialingFrom the region from which the number is dialed 184 * @return whether the short number matches a valid pattern 185 */ isValidShortNumberForRegion(PhoneNumber number, String regionDialingFrom)186 public boolean isValidShortNumberForRegion(PhoneNumber number, String regionDialingFrom) { 187 if (!regionDialingFromMatchesNumber(number, regionDialingFrom)) { 188 return false; 189 } 190 PhoneMetadata phoneMetadata = getShortNumberMetadataForRegion(regionDialingFrom); 191 if (phoneMetadata == null) { 192 return false; 193 } 194 String shortNumber = getNationalSignificantNumber(number); 195 PhoneNumberDesc generalDesc = phoneMetadata.getGeneralDesc(); 196 if (!matchesPossibleNumberAndNationalNumber(shortNumber, generalDesc)) { 197 return false; 198 } 199 PhoneNumberDesc shortNumberDesc = phoneMetadata.getShortCode(); 200 return matchesPossibleNumberAndNationalNumber(shortNumber, shortNumberDesc); 201 } 202 203 /** 204 * Tests whether a short number matches a valid pattern. If a country calling code is shared by 205 * multiple regions, this returns true if it's valid in any of them. Note that this doesn't verify 206 * the number is actually in use, which is impossible to tell by just looking at the number 207 * itself. See {@link #isValidShortNumberForRegion(PhoneNumber, String)} for details. 208 * 209 * @param number the short number for which we want to test the validity 210 * @return whether the short number matches a valid pattern 211 */ isValidShortNumber(PhoneNumber number)212 public boolean isValidShortNumber(PhoneNumber number) { 213 List<String> regionCodes = getRegionCodesForCountryCode(number.getCountryCode()); 214 String regionCode = getRegionCodeForShortNumberFromRegionList(number, regionCodes); 215 if (regionCodes.size() > 1 && regionCode != null) { 216 // If a matching region had been found for the phone number from among two or more regions, 217 // then we have already implicitly verified its validity for that region. 218 return true; 219 } 220 return isValidShortNumberForRegion(number, regionCode); 221 } 222 223 /** 224 * Gets the expected cost category of a short number when dialed from a region (however, nothing 225 * is implied about its validity). If it is important that the number is valid, then its validity 226 * must first be checked using {@link #isValidShortNumberForRegion}. Note that emergency numbers 227 * are always considered toll-free. Example usage: 228 * <pre>{@code 229 * // The region for which the number was parsed and the region we subsequently check against 230 * // need not be the same. Here we parse the number in the US and check it for Canada. 231 * PhoneNumber number = phoneUtil.parse("110", "US"); 232 * ... 233 * String regionCode = "CA"; 234 * ShortNumberInfo shortInfo = ShortNumberInfo.getInstance(); 235 * if (shortInfo.isValidShortNumberForRegion(shortNumber, regionCode)) { 236 * ShortNumberCost cost = shortInfo.getExpectedCostForRegion(number, regionCode); 237 * // Do something with the cost information here. 238 * }}</pre> 239 * 240 * @param number the short number for which we want to know the expected cost category 241 * @param regionDialingFrom the region from which the number is dialed 242 * @return the expected cost category for that region of the short number. Returns UNKNOWN_COST if 243 * the number does not match a cost category. Note that an invalid number may match any cost 244 * category. 245 */ getExpectedCostForRegion(PhoneNumber number, String regionDialingFrom)246 public ShortNumberCost getExpectedCostForRegion(PhoneNumber number, String regionDialingFrom) { 247 if (!regionDialingFromMatchesNumber(number, regionDialingFrom)) { 248 return ShortNumberCost.UNKNOWN_COST; 249 } 250 // Note that regionDialingFrom may be null, in which case phoneMetadata will also be null. 251 PhoneMetadata phoneMetadata = getShortNumberMetadataForRegion(regionDialingFrom); 252 if (phoneMetadata == null) { 253 return ShortNumberCost.UNKNOWN_COST; 254 } 255 256 String shortNumber = getNationalSignificantNumber(number); 257 258 // The possible lengths are not present for a particular sub-type if they match the general 259 // description; for this reason, we check the possible lengths against the general description 260 // first to allow an early exit if possible. 261 if (!phoneMetadata.getGeneralDesc().getPossibleLengthList().contains(shortNumber.length())) { 262 return ShortNumberCost.UNKNOWN_COST; 263 } 264 265 // The cost categories are tested in order of decreasing expense, since if for some reason the 266 // patterns overlap the most expensive matching cost category should be returned. 267 if (matchesPossibleNumberAndNationalNumber(shortNumber, phoneMetadata.getPremiumRate())) { 268 return ShortNumberCost.PREMIUM_RATE; 269 } 270 if (matchesPossibleNumberAndNationalNumber(shortNumber, phoneMetadata.getStandardRate())) { 271 return ShortNumberCost.STANDARD_RATE; 272 } 273 if (matchesPossibleNumberAndNationalNumber(shortNumber, phoneMetadata.getTollFree())) { 274 return ShortNumberCost.TOLL_FREE; 275 } 276 if (isEmergencyNumber(shortNumber, regionDialingFrom)) { 277 // Emergency numbers are implicitly toll-free. 278 return ShortNumberCost.TOLL_FREE; 279 } 280 return ShortNumberCost.UNKNOWN_COST; 281 } 282 283 /** 284 * Gets the expected cost category of a short number (however, nothing is implied about its 285 * validity). If the country calling code is unique to a region, this method behaves exactly the 286 * same as {@link #getExpectedCostForRegion(PhoneNumber, String)}. However, if the country 287 * calling code is shared by multiple regions, then it returns the highest cost in the sequence 288 * PREMIUM_RATE, UNKNOWN_COST, STANDARD_RATE, TOLL_FREE. The reason for the position of 289 * UNKNOWN_COST in this order is that if a number is UNKNOWN_COST in one region but STANDARD_RATE 290 * or TOLL_FREE in another, its expected cost cannot be estimated as one of the latter since it 291 * might be a PREMIUM_RATE number. 292 * <p> 293 * For example, if a number is STANDARD_RATE in the US, but TOLL_FREE in Canada, the expected 294 * cost returned by this method will be STANDARD_RATE, since the NANPA countries share the same 295 * country calling code. 296 * <p> 297 * Note: If the region from which the number is dialed is known, it is highly preferable to call 298 * {@link #getExpectedCostForRegion(PhoneNumber, String)} instead. 299 * 300 * @param number the short number for which we want to know the expected cost category 301 * @return the highest expected cost category of the short number in the region(s) with the given 302 * country calling code 303 */ getExpectedCost(PhoneNumber number)304 public ShortNumberCost getExpectedCost(PhoneNumber number) { 305 List<String> regionCodes = getRegionCodesForCountryCode(number.getCountryCode()); 306 if (regionCodes.size() == 0) { 307 return ShortNumberCost.UNKNOWN_COST; 308 } 309 if (regionCodes.size() == 1) { 310 return getExpectedCostForRegion(number, regionCodes.get(0)); 311 } 312 ShortNumberCost cost = ShortNumberCost.TOLL_FREE; 313 for (String regionCode : regionCodes) { 314 ShortNumberCost costForRegion = getExpectedCostForRegion(number, regionCode); 315 switch (costForRegion) { 316 case PREMIUM_RATE: 317 return ShortNumberCost.PREMIUM_RATE; 318 case UNKNOWN_COST: 319 cost = ShortNumberCost.UNKNOWN_COST; 320 break; 321 case STANDARD_RATE: 322 if (cost != ShortNumberCost.UNKNOWN_COST) { 323 cost = ShortNumberCost.STANDARD_RATE; 324 } 325 break; 326 case TOLL_FREE: 327 // Do nothing. 328 break; 329 default: 330 logger.log(Level.SEVERE, "Unrecognised cost for region: " + costForRegion); 331 } 332 } 333 return cost; 334 } 335 336 // Helper method to get the region code for a given phone number, from a list of possible region 337 // codes. If the list contains more than one region, the first region for which the number is 338 // valid is returned. getRegionCodeForShortNumberFromRegionList(PhoneNumber number, List<String> regionCodes)339 private String getRegionCodeForShortNumberFromRegionList(PhoneNumber number, 340 List<String> regionCodes) { 341 if (regionCodes.size() == 0) { 342 return null; 343 } else if (regionCodes.size() == 1) { 344 return regionCodes.get(0); 345 } 346 String nationalNumber = getNationalSignificantNumber(number); 347 for (String regionCode : regionCodes) { 348 PhoneMetadata phoneMetadata = getShortNumberMetadataForRegion(regionCode); 349 if (phoneMetadata != null 350 && matchesPossibleNumberAndNationalNumber(nationalNumber, phoneMetadata.getShortCode())) { 351 // The number is valid for this region. 352 return regionCode; 353 } 354 } 355 return null; 356 } 357 358 /** 359 * Gets a valid short number for the specified region. 360 * 361 * @param regionCode the region for which an example short number is needed 362 * @return a valid short number for the specified region. Returns an empty string when the 363 * metadata does not contain such information. 364 */ 365 // @VisibleForTesting getExampleShortNumber(String regionCode)366 String getExampleShortNumber(String regionCode) { 367 PhoneMetadata phoneMetadata = getShortNumberMetadataForRegion(regionCode); 368 if (phoneMetadata == null) { 369 return ""; 370 } 371 PhoneNumberDesc desc = phoneMetadata.getShortCode(); 372 if (desc.hasExampleNumber()) { 373 return desc.getExampleNumber(); 374 } 375 return ""; 376 } 377 378 /** 379 * Gets a valid short number for the specified cost category. 380 * 381 * @param regionCode the region for which an example short number is needed 382 * @param cost the cost category of number that is needed 383 * @return a valid short number for the specified region and cost category. Returns an empty 384 * string when the metadata does not contain such information, or the cost is UNKNOWN_COST. 385 */ 386 // @VisibleForTesting getExampleShortNumberForCost(String regionCode, ShortNumberCost cost)387 String getExampleShortNumberForCost(String regionCode, ShortNumberCost cost) { 388 PhoneMetadata phoneMetadata = getShortNumberMetadataForRegion(regionCode); 389 if (phoneMetadata == null) { 390 return ""; 391 } 392 PhoneNumberDesc desc = null; 393 switch (cost) { 394 case TOLL_FREE: 395 desc = phoneMetadata.getTollFree(); 396 break; 397 case STANDARD_RATE: 398 desc = phoneMetadata.getStandardRate(); 399 break; 400 case PREMIUM_RATE: 401 desc = phoneMetadata.getPremiumRate(); 402 break; 403 default: 404 // UNKNOWN_COST numbers are computed by the process of elimination from the other cost 405 // categories. 406 } 407 if (desc != null && desc.hasExampleNumber()) { 408 return desc.getExampleNumber(); 409 } 410 return ""; 411 } 412 413 /** 414 * Returns true if the given number, exactly as dialed, might be used to connect to an emergency 415 * service in the given region. 416 * <p> 417 * This method accepts a string, rather than a PhoneNumber, because it needs to distinguish 418 * cases such as "+1 911" and "911", where the former may not connect to an emergency service in 419 * all cases but the latter would. This method takes into account cases where the number might 420 * contain formatting, or might have additional digits appended (when it is okay to do that in 421 * the specified region). 422 * 423 * @param number the phone number to test 424 * @param regionCode the region where the phone number is being dialed 425 * @return whether the number might be used to connect to an emergency service in the given region 426 */ connectsToEmergencyNumber(String number, String regionCode)427 public boolean connectsToEmergencyNumber(String number, String regionCode) { 428 return matchesEmergencyNumberHelper(number, regionCode, true /* allows prefix match */); 429 } 430 431 /** 432 * Returns true if the given number exactly matches an emergency service number in the given 433 * region. 434 * <p> 435 * This method takes into account cases where the number might contain formatting, but doesn't 436 * allow additional digits to be appended. Note that {@code isEmergencyNumber(number, region)} 437 * implies {@code connectsToEmergencyNumber(number, region)}. 438 * 439 * @param number the phone number to test 440 * @param regionCode the region where the phone number is being dialed 441 * @return whether the number exactly matches an emergency services number in the given region 442 */ isEmergencyNumber(CharSequence number, String regionCode)443 public boolean isEmergencyNumber(CharSequence number, String regionCode) { 444 return matchesEmergencyNumberHelper(number, regionCode, false /* doesn't allow prefix match */); 445 } 446 matchesEmergencyNumberHelper(CharSequence number, String regionCode, boolean allowPrefixMatch)447 private boolean matchesEmergencyNumberHelper(CharSequence number, String regionCode, 448 boolean allowPrefixMatch) { 449 CharSequence possibleNumber = PhoneNumberUtil.extractPossibleNumber(number); 450 if (PhoneNumberUtil.PLUS_CHARS_PATTERN.matcher(possibleNumber).lookingAt()) { 451 // Returns false if the number starts with a plus sign. We don't believe dialing the country 452 // code before emergency numbers (e.g. +1911) works, but later, if that proves to work, we can 453 // add additional logic here to handle it. 454 return false; 455 } 456 PhoneMetadata metadata = getShortNumberMetadataForRegion(regionCode); 457 if (metadata == null || !metadata.hasEmergency()) { 458 return false; 459 } 460 461 String normalizedNumber = PhoneNumberUtil.normalizeDigitsOnly(possibleNumber); 462 boolean allowPrefixMatchForRegion = 463 allowPrefixMatch && !REGIONS_WHERE_EMERGENCY_NUMBERS_MUST_BE_EXACT.contains(regionCode); 464 return matcherApi.matchNationalNumber(normalizedNumber, metadata.getEmergency(), 465 allowPrefixMatchForRegion); 466 } 467 468 /** 469 * Given a valid short number, determines whether it is carrier-specific (however, nothing is 470 * implied about its validity). Carrier-specific numbers may connect to a different end-point, or 471 * not connect at all, depending on the user's carrier. If it is important that the number is 472 * valid, then its validity must first be checked using {@link #isValidShortNumber} or 473 * {@link #isValidShortNumberForRegion}. 474 * 475 * @param number the valid short number to check 476 * @return whether the short number is carrier-specific, assuming the input was a valid short 477 * number 478 */ isCarrierSpecific(PhoneNumber number)479 public boolean isCarrierSpecific(PhoneNumber number) { 480 List<String> regionCodes = getRegionCodesForCountryCode(number.getCountryCode()); 481 String regionCode = getRegionCodeForShortNumberFromRegionList(number, regionCodes); 482 String nationalNumber = getNationalSignificantNumber(number); 483 PhoneMetadata phoneMetadata = getShortNumberMetadataForRegion(regionCode); 484 return (phoneMetadata != null) 485 && (matchesPossibleNumberAndNationalNumber(nationalNumber, 486 phoneMetadata.getCarrierSpecific())); 487 } 488 489 /** 490 * Given a valid short number, determines whether it is carrier-specific when dialed from the 491 * given region (however, nothing is implied about its validity). Carrier-specific numbers may 492 * connect to a different end-point, or not connect at all, depending on the user's carrier. If 493 * it is important that the number is valid, then its validity must first be checked using 494 * {@link #isValidShortNumber} or {@link #isValidShortNumberForRegion}. Returns false if the 495 * number doesn't match the region provided. 496 * 497 * @param number the valid short number to check 498 * @param regionDialingFrom the region from which the number is dialed 499 * @return whether the short number is carrier-specific in the provided region, assuming the 500 * input was a valid short number 501 */ isCarrierSpecificForRegion(PhoneNumber number, String regionDialingFrom)502 public boolean isCarrierSpecificForRegion(PhoneNumber number, String regionDialingFrom) { 503 if (!regionDialingFromMatchesNumber(number, regionDialingFrom)) { 504 return false; 505 } 506 String nationalNumber = getNationalSignificantNumber(number); 507 PhoneMetadata phoneMetadata = getShortNumberMetadataForRegion(regionDialingFrom); 508 return (phoneMetadata != null) 509 && (matchesPossibleNumberAndNationalNumber(nationalNumber, 510 phoneMetadata.getCarrierSpecific())); 511 } 512 513 /** 514 * Given a valid short number, determines whether it is an SMS service (however, nothing is 515 * implied about its validity). An SMS service is where the primary or only intended usage is to 516 * receive and/or send text messages (SMSs). This includes MMS as MMS numbers downgrade to SMS if 517 * the other party isn't MMS-capable. If it is important that the number is valid, then its 518 * validity must first be checked using {@link #isValidShortNumber} or {@link 519 * #isValidShortNumberForRegion}. Returns false if the number doesn't match the region provided. 520 * 521 * @param number the valid short number to check 522 * @param regionDialingFrom the region from which the number is dialed 523 * @return whether the short number is an SMS service in the provided region, assuming the input 524 * was a valid short number 525 */ isSmsServiceForRegion(PhoneNumber number, String regionDialingFrom)526 public boolean isSmsServiceForRegion(PhoneNumber number, String regionDialingFrom) { 527 if (!regionDialingFromMatchesNumber(number, regionDialingFrom)) { 528 return false; 529 } 530 PhoneMetadata phoneMetadata = getShortNumberMetadataForRegion(regionDialingFrom); 531 return phoneMetadata != null 532 && matchesPossibleNumberAndNationalNumber(getNationalSignificantNumber(number), 533 phoneMetadata.getSmsServices()); 534 } 535 536 /** 537 * Gets the national significant number of the a phone number. Note a national significant number 538 * doesn't contain a national prefix or any formatting. 539 * <p> 540 * This is a temporary duplicate of the {@code getNationalSignificantNumber} method from 541 * {@code PhoneNumberUtil}. Ultimately a canonical static version should exist in a separate 542 * utility class (to prevent {@code ShortNumberInfo} needing to depend on PhoneNumberUtil). 543 * 544 * @param number the phone number for which the national significant number is needed 545 * @return the national significant number of the PhoneNumber object passed in 546 */ getNationalSignificantNumber(PhoneNumber number)547 private static String getNationalSignificantNumber(PhoneNumber number) { 548 // If leading zero(s) have been set, we prefix this now. Note this is not a national prefix. 549 StringBuilder nationalNumber = new StringBuilder(); 550 if (number.isItalianLeadingZero()) { 551 char[] zeros = new char[number.getNumberOfLeadingZeros()]; 552 Arrays.fill(zeros, '0'); 553 nationalNumber.append(new String(zeros)); 554 } 555 nationalNumber.append(number.getNationalNumber()); 556 return nationalNumber.toString(); 557 } 558 559 // TODO: Once we have benchmarked ShortNumberInfo, consider if it is worth keeping 560 // this performance optimization. matchesPossibleNumberAndNationalNumber(String number, PhoneNumberDesc numberDesc)561 private boolean matchesPossibleNumberAndNationalNumber(String number, 562 PhoneNumberDesc numberDesc) { 563 if (numberDesc.getPossibleLengthCount() > 0 564 && !numberDesc.getPossibleLengthList().contains(number.length())) { 565 return false; 566 } 567 return matcherApi.matchNationalNumber(number, numberDesc, false); 568 } 569 } 570