1/** 2 * @license 3 * Copyright (C) 2018 The Libphonenumber Authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18/** 19 * @fileoverview Utility for international phone numbers. 20 * Functionality includes formatting, parsing and validation. 21 * (based on the java implementation). 22 * 23 * NOTE: A lot of methods in this class require Region Code strings. These must 24 * be provided using CLDR two-letter region-code format. These should be in 25 * upper-case. The list of the codes can be found here: 26 * http://www.unicode.org/cldr/charts/30/supplemental/territory_information.html 27 * 28 * @author James Wright 29 */ 30 31goog.provide('i18n.phonenumbers.ShortNumberInfo'); 32 33goog.require('goog.array'); 34goog.require('goog.proto2.PbLiteSerializer'); 35goog.require('i18n.phonenumbers.PhoneMetadata'); 36goog.require('i18n.phonenumbers.PhoneNumber'); 37goog.require('i18n.phonenumbers.PhoneNumberDesc'); 38goog.require('i18n.phonenumbers.PhoneNumberUtil'); 39goog.require('i18n.phonenumbers.metadata'); 40goog.require('i18n.phonenumbers.shortnumbermetadata'); 41 42 43 44/** 45 * @constructor 46 * @private 47 */ 48i18n.phonenumbers.ShortNumberInfo = function() { 49 /** 50 * A mapping from region code to the short-number metadata for that region. 51 * @type {Object.<string, i18n.phonenumbers.PhoneMetadata>} 52 */ 53 this.regionToMetadataMap = {}; 54}; 55goog.addSingletonGetter(i18n.phonenumbers.ShortNumberInfo); 56 57 58/** 59 * In these countries, if extra digits are added to an emergency number, it no 60 * longer connects to the emergency service. 61 * @const 62 * @type {!Array<string>} 63 * @private 64 */ 65i18n.phonenumbers.ShortNumberInfo. 66 REGIONS_WHERE_EMERGENCY_NUMBERS_MUST_BE_EXACT_ = [ 67 'BR', 68 'CL', 69 'NI' 70 ]; 71 72 73/** 74 * @enum {number} Cost categories of short numbers. 75 */ 76i18n.phonenumbers.ShortNumberInfo.ShortNumberCost = { 77 TOLL_FREE: 0, 78 STANDARD_RATE: 1, 79 PREMIUM_RATE: 2, 80 UNKNOWN_COST: 3 81}; 82 83 84/** 85 * Returns a list with the region codes that match the specific country calling 86 * code. For non-geographical country calling codes, the region code 001 is 87 * returned. Also, in the case of no region code being found, an empty list 88 * is returned. 89 * @param {number} countryCallingCode 90 * @return {!Array<string>} The region codes that match the given country code. 91 * @private 92 */ 93i18n.phonenumbers.ShortNumberInfo.prototype.getRegionCodesForCountryCode_ = 94 function(countryCallingCode) { 95 var regionCodes = i18n.phonenumbers.metadata 96 .countryCodeToRegionCodeMap[countryCallingCode]; 97 return regionCodes ? regionCodes : []; 98}; 99 100 101/** 102 * Helper method to check that the country calling code of the number matches 103 * the region it's being dialed from. 104 * @param {i18n.phonenumbers.PhoneNumber} number 105 * @param {?string} regionDialingFrom 106 * @return {boolean} 107 * @private 108 */ 109i18n.phonenumbers.ShortNumberInfo.prototype.regionDialingFromMatchesNumber_ = 110 function(number, regionDialingFrom) { 111 var regionCodes = this.getRegionCodesForCountryCode_( 112 number.getCountryCodeOrDefault()); 113 return goog.array.contains(regionCodes, regionDialingFrom); 114}; 115 116 117/** 118 * Check whether a short number is a possible number when dialed from the given 119 * region. This provides a more lenient check than 120 * {@link #isValidShortNumberForRegion}. 121 * 122 * @param {i18n.phonenumbers.PhoneNumber} number the short number to check 123 * @param {string} regionDialingFrom the region from which the number is dialed 124 * @return {boolean} whether the number is a possible short number 125 */ 126i18n.phonenumbers.ShortNumberInfo.prototype.isPossibleShortNumberForRegion = 127 function(number, regionDialingFrom) { 128 if (!this.regionDialingFromMatchesNumber_(number, regionDialingFrom)) { 129 return false; 130 } 131 var phoneMetadata = this.getMetadataForRegion_(regionDialingFrom); 132 if (!phoneMetadata) { 133 return false; 134 } 135 var numberLength = this.getNationalSignificantNumber_(number).length; 136 return goog.array.contains( 137 phoneMetadata.getGeneralDesc().possibleLengthArray(), numberLength); 138}; 139 140 141/** 142 * Check whether a short number is a possible number. If a country calling code 143 * is shared by multiple regions, this returns true if it's possible in any of 144 * them. This provides a more lenient check than {@link #isValidShortNumber}. 145 * See {@link #isPossibleShortNumberForRegion(PhoneNumber, String)} for details. 146 * 147 * @param {i18n.phonenumbers.PhoneNumber} number the short number to check 148 * @return {boolean} whether the number is a possible short number 149 */ 150i18n.phonenumbers.ShortNumberInfo.prototype.isPossibleShortNumber = 151 function(number) { 152 var regionCodes = this.getRegionCodesForCountryCode_( 153 number.getCountryCodeOrDefault()); 154 var shortNumberLength = this.getNationalSignificantNumber_(number).length; 155 for (var i = 0; i < regionCodes.length; i++) { 156 var region = regionCodes[i]; 157 var phoneMetadata = this.getMetadataForRegion_(region); 158 if (!phoneMetadata) { 159 continue; 160 } 161 var possibleLengths = phoneMetadata.getGeneralDesc().possibleLengthArray(); 162 if (goog.array.contains(possibleLengths, shortNumberLength)) { 163 return true; 164 } 165 } 166 return false; 167}; 168 169 170/** 171 * Tests whether a short number matches a valid pattern in a region. Note that 172 * this doesn't verify the number is actually in use, which is impossible to 173 * tell by just looking at the number itself. 174 * 175 * @param {i18n.phonenumbers.PhoneNumber} number the short number for which we 176 * want to test the validity 177 * @param {?string} regionDialingFrom the region from which the number is dialed 178 * @return {boolean} whether the short number matches a valid pattern 179 */ 180i18n.phonenumbers.ShortNumberInfo.prototype.isValidShortNumberForRegion = 181 function(number, regionDialingFrom) { 182 if (!this.regionDialingFromMatchesNumber_(number, regionDialingFrom)) { 183 return false; 184 } 185 var phoneMetadata = this.getMetadataForRegion_(regionDialingFrom); 186 if (!phoneMetadata) { 187 return false; 188 } 189 var shortNumber = this.getNationalSignificantNumber_(number); 190 var generalDesc = phoneMetadata.getGeneralDesc(); 191 if (!this.matchesPossibleNumberAndNationalNumber_(shortNumber, generalDesc)) { 192 return false; 193 } 194 var shortNumberDesc = phoneMetadata.getShortCode(); 195 return this.matchesPossibleNumberAndNationalNumber_(shortNumber, 196 shortNumberDesc); 197}; 198 199 200/** 201 * Tests whether a short number matches a valid pattern. If a country calling 202 * code is shared by multiple regions, this returns true if it's valid in any of 203 * them. Note that this doesn't verify the number is actually in use, which is 204 * impossible to tell by just looking at the number itself. See 205 * {@link #isValidShortNumberForRegion(PhoneNumber, String)} for details. 206 * 207 * @param {i18n.phonenumbers.PhoneNumber} number the short number for which we 208 * want to test the validity 209 * @return {boolean} whether the short number matches a valid pattern 210 */ 211i18n.phonenumbers.ShortNumberInfo.prototype.isValidShortNumber = 212 function(number) { 213 var regionCodes = this.getRegionCodesForCountryCode_( 214 number.getCountryCodeOrDefault()); 215 var regionCode = this.getRegionCodeForShortNumberFromRegionList_(number, 216 regionCodes); 217 if (regionCodes.length > 1 && regionCode != null) { 218 // If a matching region had been found for the phone number from among two 219 // or more regions, then we have already implicitly verified its validity 220 // for that region. 221 return true; 222 } 223 return this.isValidShortNumberForRegion(number, regionCode); 224}; 225 226 227/** 228 * Gets the expected cost category of a short number when dialed from a region 229 * (however, nothing is implied about its validity). If it is important that the 230 * number is valid, then its validity must first be checked using 231 * {@link #isValidShortNumberForRegion}. Note that emergency numbers are always 232 * considered toll-free. Example usage: 233 * <pre>{@code 234 * // The region for which the number was parsed and the region we subsequently 235 * // check against need not be the same. Here we parse the number in the US and 236 * // check it for Canada. 237 * PhoneNumber number = phoneUtil.parse("110", "US"); 238 * ... 239 * String regionCode = "CA"; 240 * ShortNumberInfo shortInfo = ShortNumberInfo.getInstance(); 241 * if (shortInfo.isValidShortNumberForRegion(shortNumber, regionCode)) { 242 * ShortNumberCost cost = shortInfo.getExpectedCostForRegion(number, 243 * regionCode); 244 * // Do something with the cost information here. 245 * }}</pre> 246 * 247 * @param {i18n.phonenumbers.PhoneNumber} number the short number for which we 248 * want to know the expected cost category 249 * @param {string} regionDialingFrom the region from which the number is dialed 250 * @return {i18n.phonenumbers.ShortNumberInfo.ShortNumberCost} the expected cost 251 * category for that region of the short number. Returns UNKNOWN_COST if the 252 * number does not match a cost category. Note that an invalid number may 253 * match any cost category. 254 * @package 255 */ 256// @VisibleForTesting 257i18n.phonenumbers.ShortNumberInfo.prototype.getExpectedCostForRegion = 258 function(number, regionDialingFrom) { 259 var ShortNumberCost = i18n.phonenumbers.ShortNumberInfo.ShortNumberCost; 260 if (!this.regionDialingFromMatchesNumber_(number, regionDialingFrom)) { 261 return ShortNumberCost.UNKNOWN_COST; 262 } 263 var phoneMetadata = this.getMetadataForRegion_(regionDialingFrom); 264 if (!phoneMetadata) { 265 return ShortNumberCost.UNKNOWN_COST; 266 } 267 var shortNumber = this.getNationalSignificantNumber_(number); 268 269 if (!goog.array.contains(phoneMetadata.getGeneralDesc().possibleLengthArray(), 270 shortNumber.length)) { 271 return ShortNumberCost.UNKNOWN_COST; 272 } 273 if (this.matchesPossibleNumberAndNationalNumber_( 274 shortNumber, phoneMetadata.getPremiumRate())) { 275 return ShortNumberCost.PREMIUM_RATE; 276 } 277 if (this.matchesPossibleNumberAndNationalNumber_( 278 shortNumber, phoneMetadata.getStandardRate())) { 279 return ShortNumberCost.STANDARD_RATE; 280 } 281 if (this.matchesPossibleNumberAndNationalNumber_( 282 shortNumber, phoneMetadata.getTollFree())) { 283 return ShortNumberCost.TOLL_FREE; 284 } 285 if (this.isEmergencyNumber(shortNumber, regionDialingFrom)) { 286 // Emergency numbers are implicitly toll-free 287 return ShortNumberCost.TOLL_FREE; 288 } 289 return ShortNumberCost.UNKNOWN_COST; 290}; 291 292 293/** 294 * Gets the expected cost category of a short number (however, nothing is 295 * implied about its validity). If the country calling code is unique to a 296 * region, this method behaves exactly the same as 297 * {@link #getExpectedCostForRegion(PhoneNumber, String)}. However, if the 298 * country calling code is shared by multiple regions, then it returns the 299 * highest cost in the sequence PREMIUM_RATE, UNKNOWN_COST, STANDARD_RATE, 300 * TOLL_FREE. The reason for the position of UNKNOWN_COST in this order is that 301 * if a number is UNKNOWN_COST in one region but STANDARD_RATE or TOLL_FREE in 302 * another, its expected cost cannot be estimated as one of the latter since it 303 * might be a PREMIUM_RATE number. 304 * <p> 305 * For example, if a number is STANDARD_RATE in the US, but TOLL_FREE in Canada, 306 * the expected cost returned by this method will be STANDARD_RATE, since the 307 * NANPA countries share the same country calling code. 308 * <p> 309 * Note: If the region from which the number is dialed is known, it is highly 310 * preferable to call {@link #getExpectedCostForRegion(PhoneNumber, String)} 311 * instead. 312 * 313 * @param {i18n.phonenumbers.PhoneNumber} number the short number for which we 314 * want to know the expected cost category 315 * @return {i18n.phonenumbers.ShortNumberInfo.ShortNumberCost} the highest 316 * expected cost category of the short number in the region(s) with the 317 * given country calling code 318 * @package 319 */ 320// @VisibleForTesting 321i18n.phonenumbers.ShortNumberInfo.prototype.getExpectedCost = function(number) { 322 var ShortNumberCost = i18n.phonenumbers.ShortNumberInfo.ShortNumberCost; 323 var regionCodes = this.getRegionCodesForCountryCode_( 324 number.getCountryCodeOrDefault()); 325 if (regionCodes.length === 0) { 326 return ShortNumberCost.UNKNOWN_COST; 327 } 328 if (regionCodes.length === 1) { 329 return this.getExpectedCostForRegion(number, regionCodes[0]); 330 } 331 var cost = ShortNumberCost.TOLL_FREE; 332 for (var i = 0; i < regionCodes.length; i++) { 333 var regionCode = regionCodes[i]; 334 var costForRegion = this.getExpectedCostForRegion(number, regionCode); 335 switch (costForRegion) { 336 case ShortNumberCost.PREMIUM_RATE: 337 return ShortNumberCost.PREMIUM_RATE; 338 case ShortNumberCost.UNKNOWN_COST: 339 cost = ShortNumberCost.UNKNOWN_COST; 340 break; 341 case ShortNumberCost.STANDARD_RATE: 342 if (cost !== ShortNumberCost.UNKNOWN_COST) { 343 cost = ShortNumberCost.STANDARD_RATE; 344 } 345 break; 346 case ShortNumberCost.TOLL_FREE: 347 // Do nothing. 348 break; 349 default: 350 throw new Error('Unrecognized cost for region: ' + costForRegion); 351 } 352 } 353 return cost; 354}; 355 356 357/** 358 * Helper method to get the region code for a given phone number, from a list 359 * of possible region codes. If the list contains more than one region, the 360 * first region for which the number is valid is returned. 361 * @param {!i18n.phonenumbers.PhoneNumber} number 362 * @param {Array<string>} regionCodes 363 * @return {?string} 364 * @private 365 */ 366i18n.phonenumbers.ShortNumberInfo.prototype.getRegionCodeForShortNumberFromRegionList_ = 367 function(number, regionCodes) { 368 if (regionCodes.length === 0) { 369 return null; 370 } else if (regionCodes.length === 1) { 371 return regionCodes[0]; 372 } 373 var nationalNumber = this.getNationalSignificantNumber_(number); 374 for (var i = 0; i < regionCodes.length; i++) { 375 var regionCode = regionCodes[i]; 376 var phoneMetadata = this.getMetadataForRegion_(regionCode); 377 if (phoneMetadata && this.matchesPossibleNumberAndNationalNumber_( 378 nationalNumber, phoneMetadata.getShortCode())) { 379 return regionCode; 380 } 381 } 382 return null; 383}; 384 385 386/** 387 * Convenience method to get a list of what regions the library has metadata for 388 * @return {!Array<string>} the list of region codes 389 * @package 390 */ 391i18n.phonenumbers.ShortNumberInfo.prototype.getSupportedRegions = function() { 392 return goog.array.filter( 393 Object.keys(i18n.phonenumbers.shortnumbermetadata.countryToMetadata), 394 function(regionCode) { 395 return isNaN(regionCode); 396 }); 397}; 398 399 400/** 401 * Gets a valid short number for the specified region. 402 * 403 * @param {?string} regionCode the region for which an example short number is 404 * needed 405 * @return {string} a valid short number for the specified region. Returns an 406 * empty string when the metadata does not contain such information. 407 * @package 408 */ 409i18n.phonenumbers.ShortNumberInfo.prototype.getExampleShortNumber = 410 function(regionCode) { 411 var phoneMetadata = this.getMetadataForRegion_(regionCode); 412 if (!phoneMetadata) { 413 return ''; 414 } 415 var desc = phoneMetadata.getShortCode(); 416 if (desc.hasExampleNumber()) { 417 return desc.getExampleNumber() || ''; 418 } 419 return ''; 420}; 421 422 423/** 424 * Gets a valid short number for the specified cost category. 425 * 426 * @param {string} regionCode the region for which an example short number is 427 * needed 428 * @param {i18n.phonenumbers.ShortNumberInfo.ShortNumberCost} cost the cost 429 * category of number that is needed 430 * @return {string} a valid short number for the specified region and cost 431 * category. Returns an empty string when the metadata does not contain such 432 * information, or the cost is UNKNOWN_COST. 433 */ 434i18n.phonenumbers.ShortNumberInfo.prototype.getExampleShortNumberForCost = 435 function(regionCode, cost) { 436 var phoneMetadata = this.getMetadataForRegion_(regionCode); 437 if (!phoneMetadata) { 438 return ''; 439 } 440 var ShortNumberCost = i18n.phonenumbers.ShortNumberInfo.ShortNumberCost; 441 var desc = null; 442 switch (cost) { 443 case ShortNumberCost.TOLL_FREE: 444 desc = phoneMetadata.getTollFree(); 445 break; 446 case ShortNumberCost.STANDARD_RATE: 447 desc = phoneMetadata.getStandardRate(); 448 break; 449 case ShortNumberCost.PREMIUM_RATE: 450 desc = phoneMetadata.getPremiumRate(); 451 break; 452 default: 453 // UNKNOWN_COST numbers are computed by the process of elimination from 454 // the other cost categories. 455 } 456 if (desc && desc.hasExampleNumber()) { 457 return desc.getExampleNumber() || ''; 458 } 459 return ''; 460}; 461 462 463/** 464 * Returns true if the given number, exactly as dialed, might be used to 465 * connect to an emergency service in the given region. 466 * <p> 467 * This method accepts a string, rather than a PhoneNumber, because it needs 468 * to distinguish cases such as "+1 911" and "911", where the former may not 469 * connect to an emergency service in all cases but the latter would. This 470 * method takes into account cases where the number might contain formatting, 471 * or might have additional digits appended (when it is okay to do that in 472 * the specified region). 473 * 474 * @param {string} number the phone number to test 475 * @param {string} regionCode the region where the phone number is being 476 * dialed 477 * @return {boolean} whether the number might be used to connect to an 478 * emergency service in the given region 479 */ 480i18n.phonenumbers.ShortNumberInfo.prototype.connectsToEmergencyNumber = 481 function(number, regionCode) { 482 return this.matchesEmergencyNumberHelper_(number, regionCode, 483 true /* allows prefix match */); 484}; 485 486 487/** 488 * Returns true if the given number exactly matches an emergency service 489 * number in the given region. 490 * <p> 491 * This method takes into account cases where the number might contain 492 * formatting, but doesn't allow additional digits to be appended. Note that 493 * {@code isEmergencyNumber(number, region)} implies 494 * {@code connectsToEmergencyNumber(number, region)}. 495 * 496 * @param {string} number the phone number to test 497 * @param {string} regionCode the region where the phone number is being 498 * dialed 499 * @return {boolean} whether the number exactly matches an emergency services 500 * number in the given region. 501 */ 502i18n.phonenumbers.ShortNumberInfo.prototype.isEmergencyNumber = 503 function(number, regionCode) { 504 return this.matchesEmergencyNumberHelper_(number, regionCode, 505 false /* doesn't allow prefix match */); 506}; 507 508 509/** 510 * @param {?string} regionCode The region code to get metadata for 511 * @return {?i18n.phonenumbers.PhoneMetadata} The region code's metadata, or 512 * null if it is not available or the region code is invalid. 513 * @private 514 */ 515i18n.phonenumbers.ShortNumberInfo.prototype.getMetadataForRegion_ = 516 function(regionCode) { 517 if (!regionCode) { 518 return null; 519 } 520 regionCode = regionCode.toUpperCase(); 521 var metadata = this.regionToMetadataMap[regionCode]; 522 if (metadata == null) { 523 /** @type {goog.proto2.PbLiteSerializer} */ 524 var serializer = new goog.proto2.PbLiteSerializer(); 525 var metadataSerialized = 526 i18n.phonenumbers.shortnumbermetadata.countryToMetadata[regionCode]; 527 if (metadataSerialized == null) { 528 return null; 529 } 530 metadata = /** @type {i18n.phonenumbers.PhoneMetadata} */ ( 531 serializer.deserialize(i18n.phonenumbers.PhoneMetadata.getDescriptor(), 532 metadataSerialized)); 533 this.regionToMetadataMap[regionCode] = metadata; 534 } 535 return metadata; 536}; 537 538 539/** 540 * @param {string} number the number to match against 541 * @param {string} regionCode the region code to check against 542 * @param {boolean} allowPrefixMatch whether to allow prefix matching 543 * @return {boolean} True iff the number matches an emergency number for that 544 * particular region. 545 * @private 546 */ 547i18n.phonenumbers.ShortNumberInfo.prototype.matchesEmergencyNumberHelper_ = 548 function(number, regionCode, allowPrefixMatch) { 549 var possibleNumber = i18n.phonenumbers.PhoneNumberUtil 550 .extractPossibleNumber(number); 551 if (i18n.phonenumbers.PhoneNumberUtil.LEADING_PLUS_CHARS_PATTERN 552 .test(possibleNumber)) { 553 return false; 554 } 555 var metadata = this.getMetadataForRegion_(regionCode); 556 if (metadata == null || !metadata.hasEmergency()) { 557 return false; 558 } 559 560 var normalizedNumber = i18n.phonenumbers.PhoneNumberUtil 561 .normalizeDigitsOnly(possibleNumber); 562 var allowPrefixMatchForRegion = allowPrefixMatch && !goog.array.contains( 563 i18n.phonenumbers.ShortNumberInfo. 564 REGIONS_WHERE_EMERGENCY_NUMBERS_MUST_BE_EXACT_, 565 regionCode); 566 var emergencyNumberPattern = metadata.getEmergency() 567 .getNationalNumberPatternOrDefault(); 568 var result = i18n.phonenumbers.PhoneNumberUtil.matchesEntirely( 569 emergencyNumberPattern, normalizedNumber); 570 return result || 571 (allowPrefixMatchForRegion && 572 i18n.phonenumbers.PhoneNumberUtil 573 .matchesPrefix(emergencyNumberPattern, normalizedNumber)); 574}; 575 576 577/** 578 * Given a valid short number, determines whether it is carrier-specific 579 * (however, nothing is implied about its validity). Carrier-specific numbers 580 * may connect to a different end-point, or not connect at all, depending on 581 * the user's carrier. If it is important that the number is valid, then its 582 * validity must first be checked using {@link #isValidShortNumber} or 583 * {@link #isValidShortNumberForRegion}. 584 * 585 * @param {i18n.phonenumbers.PhoneNumber} number the valid short number to 586 * check 587 * @return {boolean} whether the short number is carrier-specific, assuming the 588 * input was a valid short number 589 */ 590i18n.phonenumbers.ShortNumberInfo.prototype.isCarrierSpecific = 591 function(number) { 592 var regionCodes = this.getRegionCodesForCountryCode_( 593 number.getCountryCodeOrDefault()); 594 var regionCode = this.getRegionCodeForShortNumberFromRegionList_(number, 595 regionCodes); 596 var nationalNumber = this.getNationalSignificantNumber_(number); 597 var phoneMetadata = this.getMetadataForRegion_(regionCode); 598 return !!phoneMetadata && this.matchesPossibleNumberAndNationalNumber_( 599 nationalNumber, phoneMetadata.getCarrierSpecific()); 600}; 601 602 603/** 604 * Given a valid short number, determines whether it is carrier-specific when 605 * dialed from the given region (however, nothing is implied about its 606 * validity). Carrier-specific numbers may connect to a different end-point, or 607 * not connect at all, depending on the user's carrier. If it is important that 608 * the number is valid, then its validity must first be checked using 609 * {@link #isValidShortNumber} or {@link #isValidShortNumberForRegion}. Returns 610 * false if the number doesn't match the region provided. 611 * 612 * @param {i18n.phonenumbers.PhoneNumber} number the valid short number to 613 * check 614 * @param {string} regionDialingFrom the region from which the number is dialed 615 * @return {boolean} whether the short number is carrier-specific in the 616 * provided region, assuming the input was a valid short number 617 */ 618i18n.phonenumbers.ShortNumberInfo.prototype.isCarrierSpecificForRegion = 619 function(number, regionDialingFrom) { 620 if (!this.regionDialingFromMatchesNumber_(number, regionDialingFrom)) { 621 return false; 622 } 623 var nationalNumber = this.getNationalSignificantNumber_(number); 624 var phoneMetadata = this.getMetadataForRegion_(regionDialingFrom); 625 return !!phoneMetadata && this.matchesPossibleNumberAndNationalNumber_( 626 nationalNumber, phoneMetadata.getCarrierSpecific()); 627}; 628 629 630/** 631 * Given a valid short number, determines whether it is an SMS service 632 * (however, nothing is implied about its validity). An SMS service is where the 633 * primary or only intended usage is to receive and/or send text messages 634 * (SMSs). This includes MMS as MMS numbers downgrade to SMS if the other party 635 * isn't MMS-capable. If it is important that the number is valid, then its 636 * validity must first be checked using {@link #isValidShortNumber} or {@link 637 * #isValidShortNumberForRegion}. Returns false if the number doesn't match the 638 * region provided. 639 * 640 * @param {i18n.phonenumbers.PhoneNumber} number the valid short number to 641 * check 642 * @param {string} regionDialingFrom the region from which the number is dialed 643 * @return {boolean} whether the short number is an SMS service in the provided 644 * region, assuming the input was a valid short number 645 */ 646i18n.phonenumbers.ShortNumberInfo.prototype.isSmsServiceForRegion = 647 function(number, regionDialingFrom) { 648 if (!this.regionDialingFromMatchesNumber_(number, regionDialingFrom)) { 649 return false; 650 } 651 var phoneMetadata = this.getMetadataForRegion_(regionDialingFrom); 652 var nationalNumber = this.getNationalSignificantNumber_(number); 653 return !!phoneMetadata && this.matchesPossibleNumberAndNationalNumber_( 654 nationalNumber, phoneMetadata.getSmsServices()); 655}; 656 657 658/** 659 * Gets the national significant number of a phone number. Note a national 660 * significant number doesn't contain a national prefix or any formatting. 661 * <p> 662 * This is a temporary duplicate of the {@code getNationalSignificantNumber} 663 * method from {@code PhoneNumberUtil}. Ultimately a canonical static version 664 * should exist in a separate utility class (to prevent {@code ShortNumberInfo} 665 * needing to depend on PhoneNumberUtil). 666 * 667 * @param {i18n.phonenumbers.PhoneNumber} number the phone number for which the 668 * national significant number is needed. 669 * @return {string} the national significant number of the PhoneNumber object 670 * passed in. 671 * @private 672 */ 673i18n.phonenumbers.ShortNumberInfo.prototype.getNationalSignificantNumber_ = 674 function(number) { 675 if (!number.hasNationalNumber()) { 676 return ''; 677 } 678 /** @type {string} */ 679 var nationalNumber = '' + number.getNationalNumber(); 680 // If leading zero(s) have been set, we prefix this now. Note that a single 681 // leading zero is not the same as a national prefix; leading zeros should be 682 // dialled no matter whether you are dialling from within or outside the 683 // country, national prefixes are added when formatting nationally if 684 // applicable. 685 if (number.hasItalianLeadingZero() && number.getItalianLeadingZero() && 686 number.getNumberOfLeadingZerosOrDefault() > 0) { 687 return Array(number.getNumberOfLeadingZerosOrDefault() + 1).join('0') + 688 nationalNumber; 689 } 690 return nationalNumber; 691}; 692 693 694/** 695 * Helper method to add in a performance optimization. 696 * TODO: Once we have benchmarked ShortNumberInfo, consider if it is worth 697 * keeping this performance optimization. 698 * @param {string} number 699 * @param {i18n.phonenumbers.PhoneNumberDesc} numberDesc 700 * @return {boolean} 701 * @private 702 */ 703i18n.phonenumbers.ShortNumberInfo.prototype.matchesPossibleNumberAndNationalNumber_ = 704 function(number, numberDesc) { 705 if (numberDesc.possibleLengthArray().length > 0 && !goog.array.contains( 706 numberDesc.possibleLengthArray(), number.length)) { 707 return false; 708 } 709 return i18n.phonenumbers.PhoneNumberUtil.matchesEntirely( 710 numberDesc.getNationalNumberPatternOrDefault(), number.toString()); 711}; 712