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. */
getInstance()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
ShortNumberInfo(MatcherApi matcherApi)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    */
getRegionCodesForCountryCode(int countryCallingCode)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
isPossibleShortNumberForRegion(String shortNumber, String regionDialingFrom)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    */
isPossibleShortNumberForRegion(PhoneNumber number, String regionDialingFrom)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    */
isPossibleShortNumber(PhoneNumber number)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
isValidShortNumberForRegion(String shortNumber, String regionDialingFrom)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    */
isValidShortNumberForRegion(PhoneNumber number, String regionDialingFrom)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    */
isValidShortNumber(PhoneNumber number)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
getExpectedCostForRegion(String shortNumber, String regionDialingFrom)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    */
getExpectedCostForRegion(PhoneNumber number, String regionDialingFrom)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    */
getExpectedCost(PhoneNumber number)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.
getRegionCodeForShortNumberFromRegionList(PhoneNumber number, List<String> regionCodes)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    */
getSupportedRegions()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
getExampleShortNumber(String regionCode)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
getExampleShortNumberForCost(String regionCode, ShortNumberCost cost)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    */
connectsToEmergencyNumber(String number, String regionCode)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    */
isEmergencyNumber(String number, String regionCode)511   public boolean isEmergencyNumber(String number, String regionCode) {
512     return matchesEmergencyNumberHelper(number, regionCode, false /* doesn't allow prefix match */);
513   }
514 
matchesEmergencyNumberHelper(String number, String regionCode, boolean allowPrefixMatch)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    */
isCarrierSpecific(PhoneNumber number)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    */
getNationalSignificantNumber(PhoneNumber number)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.
matchesPossibleNumberAndNationalNumber(String number, PhoneNumberDesc numberDesc)582   private boolean matchesPossibleNumberAndNationalNumber(String number,
583       PhoneNumberDesc numberDesc) {
584     return matcherApi.matchesPossibleNumber(number, numberDesc)
585         && matcherApi.matchesNationalNumber(number, numberDesc, false);
586   }
587 }
588