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