/* GENERATED SOURCE. DO NOT MODIFY. */ /* * Copyright (C) 2009 The Libphonenumber Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.i18n.phonenumbers; import com.android.i18n.phonenumbers.Phonemetadata.NumberFormat; import com.android.i18n.phonenumbers.Phonemetadata.PhoneMetadata; import com.android.i18n.phonenumbers.Phonemetadata.PhoneNumberDesc; import com.android.i18n.phonenumbers.Phonenumber.PhoneNumber; import com.android.i18n.phonenumbers.Phonenumber.PhoneNumber.CountryCodeSource; import com.android.i18n.phonenumbers.internal.MatcherApi; import com.android.i18n.phonenumbers.internal.RegexBasedMatcher; import com.android.i18n.phonenumbers.internal.RegexCache; import com.android.i18n.phonenumbers.metadata.DefaultMetadataDependenciesProvider; import com.android.i18n.phonenumbers.metadata.source.MetadataSource; import com.android.i18n.phonenumbers.metadata.source.MetadataSourceImpl; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Utility for international phone numbers. Functionality includes formatting, parsing and * validation. * *
If you use this library, and want to be notified about important changes, please sign up to
* our mailing list.
*
* NOTE: A lot of methods in this class require Region Code strings. These must be provided using
* CLDR two-letter region-code format. These should be in upper-case. The list of the codes
* can be found here:
* http://www.unicode.org/cldr/charts/30/supplemental/territory_information.html
* @hide This class is not part of the Android public SDK API
*/
public class PhoneNumberUtil {
private static final Logger logger = Logger.getLogger(PhoneNumberUtil.class.getName());
/** Flags to use when compiling regular expressions for phone numbers. */
static final int REGEX_FLAGS = Pattern.UNICODE_CASE | Pattern.CASE_INSENSITIVE;
// The minimum and maximum length of the national significant number.
private static final int MIN_LENGTH_FOR_NSN = 2;
// The ITU says the maximum length should be 15, but we have found longer numbers in Germany.
static final int MAX_LENGTH_FOR_NSN = 17;
// The maximum length of the country calling code.
static final int MAX_LENGTH_COUNTRY_CODE = 3;
// We don't allow input strings for parsing to be longer than 250 chars. This prevents malicious
// input from overflowing the regular-expression engine.
private static final int MAX_INPUT_STRING_LENGTH = 250;
// Region-code for the unknown region.
private static final String UNKNOWN_REGION = "ZZ";
private static final int NANPA_COUNTRY_CODE = 1;
// Map of country calling codes that use a mobile token before the area code. One example of when
// this is relevant is when determining the length of the national destination code, which should
// be the length of the area code plus the length of the mobile token.
private static final Map
* Warning: This level might result in lower coverage especially for regions outside of country
* code "+1". If you are not sure about which level to use, email the discussion group
* libphonenumber-discuss@googlegroups.com.
*/
STRICT_GROUPING {
@Override
boolean verify(
PhoneNumber number,
CharSequence candidate,
PhoneNumberUtil util,
PhoneNumberMatcher matcher) {
String candidateString = candidate.toString();
if (!util.isValidNumber(number)
|| !PhoneNumberMatcher.containsOnlyValidXChars(number, candidateString, util)
|| PhoneNumberMatcher.containsMoreThanOneSlashInNationalNumber(number, candidateString)
|| !PhoneNumberMatcher.isNationalPrefixPresentIfRequired(number, util)) {
return false;
}
return matcher.checkNumberGroupingIsValid(
number, candidate, util, new PhoneNumberMatcher.NumberGroupingChecker() {
@Override
public boolean checkGroups(PhoneNumberUtil util, PhoneNumber number,
StringBuilder normalizedCandidate,
String[] expectedNumberGroups) {
return PhoneNumberMatcher.allNumberGroupsRemainGrouped(
util, number, normalizedCandidate, expectedNumberGroups);
}
});
}
},
/**
* Phone numbers accepted are {@linkplain PhoneNumberUtil#isValidNumber(PhoneNumber) valid} and
* are grouped in the same way that we would have formatted it, or as a single block. For
* example, a US number written as "650 2530000" is not accepted at this leniency level, whereas
* "650 253 0000" or "6502530000" are.
* Numbers with more than one '/' symbol are also dropped at this level.
*
* Warning: This level might result in lower coverage especially for regions outside of country
* code "+1". If you are not sure about which level to use, email the discussion group
* libphonenumber-discuss@googlegroups.com.
*/
EXACT_GROUPING {
@Override
boolean verify(
PhoneNumber number,
CharSequence candidate,
PhoneNumberUtil util,
PhoneNumberMatcher matcher) {
String candidateString = candidate.toString();
if (!util.isValidNumber(number)
|| !PhoneNumberMatcher.containsOnlyValidXChars(number, candidateString, util)
|| PhoneNumberMatcher.containsMoreThanOneSlashInNationalNumber(number, candidateString)
|| !PhoneNumberMatcher.isNationalPrefixPresentIfRequired(number, util)) {
return false;
}
return matcher.checkNumberGroupingIsValid(
number, candidate, util, new PhoneNumberMatcher.NumberGroupingChecker() {
@Override
public boolean checkGroups(PhoneNumberUtil util, PhoneNumber number,
StringBuilder normalizedCandidate,
String[] expectedNumberGroups) {
return PhoneNumberMatcher.allNumberGroupsAreExactlyPresent(
util, number, normalizedCandidate, expectedNumberGroups);
}
});
}
};
/** Returns true if {@code number} is a verified number according to this leniency. */
abstract boolean verify(
PhoneNumber number,
CharSequence candidate,
PhoneNumberUtil util,
PhoneNumberMatcher matcher);
}
// A source of metadata for different regions.
private final MetadataSource metadataSource;
// A mapping from a country calling code to the region codes which denote the region represented
// by that country calling code. In the case of multiple regions sharing a calling code, such as
// the NANPA regions, the one indicated with "isMainCountryForCode" in the metadata should be
// first.
private final Map The {@link PhoneNumberUtil} is implemented as a singleton. Therefore, calling getInstance
* multiple times will only result in one instance being created.
*
* @return a PhoneNumberUtil instance
*/
@android.compat.annotation.UnsupportedAppUsage
public static synchronized PhoneNumberUtil getInstance() {
if (instance == null) {
MetadataLoader metadataLoader = DefaultMetadataDependenciesProvider.getInstance()
.getMetadataLoader();
setInstance(createInstance(metadataLoader));
}
return instance;
}
/**
* Create a new {@link PhoneNumberUtil} instance to carry out international phone number
* formatting, parsing, or validation. The instance is loaded with all metadata by
* using the metadataLoader specified.
*
* This method should only be used in the rare case in which you want to manage your own
* metadata loading. Calling this method multiple times is very expensive, as each time
* a new instance is created from scratch. When in doubt, use {@link #getInstance}.
*
* @param metadataLoader customized metadata loader. This should not be null
* @return a PhoneNumberUtil instance
*/
public static PhoneNumberUtil createInstance(MetadataLoader metadataLoader) {
if (metadataLoader == null) {
throw new IllegalArgumentException("metadataLoader could not be null.");
}
return createInstance(new MetadataSourceImpl(
DefaultMetadataDependenciesProvider.getInstance().getPhoneNumberMetadataFileNameProvider(),
metadataLoader,
DefaultMetadataDependenciesProvider.getInstance().getMetadataParser()
));
}
/**
* Create a new {@link PhoneNumberUtil} instance to carry out international phone number
* formatting, parsing, or validation. The instance is loaded with all metadata by
* using the metadataSource specified.
*
* This method should only be used in the rare case in which you want to manage your own
* metadata loading. Calling this method multiple times is very expensive, as each time
* a new instance is created from scratch. When in doubt, use {@link #getInstance}.
*
* @param metadataSource customized metadata source. This should not be null
* @return a PhoneNumberUtil instance
*/
private static PhoneNumberUtil createInstance(MetadataSource metadataSource) {
if (metadataSource == null) {
throw new IllegalArgumentException("metadataSource could not be null.");
}
return new PhoneNumberUtil(metadataSource,
CountryCodeToRegionCodeMap.getCountryCodeToRegionCodeMap());
}
/**
* Helper function to check if the national prefix formatting rule has the first group only, i.e.,
* does not start with the national prefix.
*/
static boolean formattingRuleHasFirstGroupOnly(String nationalPrefixFormattingRule) {
return nationalPrefixFormattingRule.length() == 0
|| FIRST_GROUP_ONLY_PREFIX_PATTERN.matcher(nationalPrefixFormattingRule).matches();
}
/**
* Tests whether a phone number has a geographical association. It checks if the number is
* associated with a certain region in the country to which it belongs. Note that this doesn't
* verify if the number is actually in use.
*/
public boolean isNumberGeographical(PhoneNumber phoneNumber) {
return isNumberGeographical(getNumberType(phoneNumber), phoneNumber.getCountryCode());
}
/**
* Overload of isNumberGeographical(PhoneNumber), since calculating the phone number type is
* expensive; if we have already done this, we don't want to do it again.
*/
public boolean isNumberGeographical(PhoneNumberType phoneNumberType, int countryCallingCode) {
return phoneNumberType == PhoneNumberType.FIXED_LINE
|| phoneNumberType == PhoneNumberType.FIXED_LINE_OR_MOBILE
|| (GEO_MOBILE_COUNTRIES.contains(countryCallingCode)
&& phoneNumberType == PhoneNumberType.MOBILE);
}
/**
* Helper function to check region code is not unknown or null.
*/
private boolean isValidRegionCode(String regionCode) {
return regionCode != null && supportedRegions.contains(regionCode);
}
/**
* Helper function to check the country calling code is valid.
*/
private boolean hasValidCountryCallingCode(int countryCallingCode) {
return countryCallingCodeToRegionCodeMap.containsKey(countryCallingCode);
}
/**
* Formats a phone number in the specified format using default rules. Note that this does not
* promise to produce a phone number that the user can dial from where they are - although we do
* format in either 'national' or 'international' format depending on what the client asks for, we
* do not currently support a more abbreviated format, such as for users in the same "area" who
* could potentially dial the number without area code. Note that if the phone number has a
* country calling code of 0 or an otherwise invalid country calling code, we cannot work out
* which formatting rules to apply so we return the national significant number with no formatting
* applied.
*
* @param number the phone number to be formatted
* @param numberFormat the format the phone number should be formatted into
* @return the formatted phone number
*/
@android.compat.annotation.UnsupportedAppUsage
public String format(PhoneNumber number, PhoneNumberFormat numberFormat) {
if (number.getNationalNumber() == 0) {
// Unparseable numbers that kept their raw input just use that.
// This is the only case where a number can be formatted as E164 without a
// leading '+' symbol (but the original number wasn't parseable anyway).
String rawInput = number.getRawInput();
if (rawInput.length() > 0 || !number.hasCountryCode()) {
return rawInput;
}
}
StringBuilder formattedNumber = new StringBuilder(20);
format(number, numberFormat, formattedNumber);
return formattedNumber.toString();
}
/**
* Same as {@link #format(PhoneNumber, PhoneNumberFormat)}, but accepts a mutable StringBuilder as
* a parameter to decrease object creation when invoked many times.
*/
public void format(PhoneNumber number, PhoneNumberFormat numberFormat,
StringBuilder formattedNumber) {
// Clear the StringBuilder first.
formattedNumber.setLength(0);
int countryCallingCode = number.getCountryCode();
String nationalSignificantNumber = getNationalSignificantNumber(number);
if (numberFormat == PhoneNumberFormat.E164) {
// Early exit for E164 case (even if the country calling code is invalid) since no formatting
// of the national number needs to be applied. Extensions are not formatted.
formattedNumber.append(nationalSignificantNumber);
prefixNumberWithCountryCallingCode(countryCallingCode, PhoneNumberFormat.E164,
formattedNumber);
return;
}
if (!hasValidCountryCallingCode(countryCallingCode)) {
formattedNumber.append(nationalSignificantNumber);
return;
}
// Note getRegionCodeForCountryCode() is used because formatting information for regions which
// share a country calling code is contained by only one region for performance reasons. For
// example, for NANPA regions it will be contained in the metadata for US.
String regionCode = getRegionCodeForCountryCode(countryCallingCode);
// Metadata cannot be null because the country calling code is valid (which means that the
// region code cannot be ZZ and must be one of our supported region codes).
PhoneMetadata metadata =
getMetadataForRegionOrCallingCode(countryCallingCode, regionCode);
formattedNumber.append(formatNsn(nationalSignificantNumber, metadata, numberFormat));
maybeAppendFormattedExtension(number, metadata, numberFormat, formattedNumber);
prefixNumberWithCountryCallingCode(countryCallingCode, numberFormat, formattedNumber);
}
/**
* Formats a phone number in the specified format using client-defined formatting rules. Note that
* if the phone number has a country calling code of zero or an otherwise invalid country calling
* code, we cannot work out things like whether there should be a national prefix applied, or how
* to format extensions, so we return the national significant number with no formatting applied.
*
* @param number the phone number to be formatted
* @param numberFormat the format the phone number should be formatted into
* @param userDefinedFormats formatting rules specified by clients
* @return the formatted phone number
*/
public String formatByPattern(PhoneNumber number,
PhoneNumberFormat numberFormat,
List Use {@link #formatNationalNumberWithCarrierCode} instead if the carrier code passed in
* should take precedence over the number's {@code preferredDomesticCarrierCode} when formatting.
*
* @param number the phone number to be formatted
* @param fallbackCarrierCode the carrier selection code to be used, if none is found in the
* phone number itself
* @return the formatted phone number in national format for dialing using the number's
* {@code preferredDomesticCarrierCode}, or the {@code fallbackCarrierCode} passed in if
* none is found
*/
public String formatNationalNumberWithPreferredCarrierCode(PhoneNumber number,
CharSequence fallbackCarrierCode) {
return formatNationalNumberWithCarrierCode(number,
// Historically, we set this to an empty string when parsing with raw input if none was
// found in the input string. However, this doesn't result in a number we can dial. For this
// reason, we treat the empty string the same as if it isn't set at all.
number.getPreferredDomesticCarrierCode().length() > 0
? number.getPreferredDomesticCarrierCode()
: fallbackCarrierCode);
}
/**
* Returns a number formatted in such a way that it can be dialed from a mobile phone in a
* specific region. If the number cannot be reached from the region (e.g. some countries block
* toll-free numbers from being called outside of the country), the method returns an empty
* string.
*
* @param number the phone number to be formatted
* @param regionCallingFrom the region where the call is being placed
* @param withFormatting whether the number should be returned with formatting symbols, such as
* spaces and dashes.
* @return the formatted phone number
*/
public String formatNumberForMobileDialing(PhoneNumber number, String regionCallingFrom,
boolean withFormatting) {
int countryCallingCode = number.getCountryCode();
if (!hasValidCountryCallingCode(countryCallingCode)) {
return number.hasRawInput() ? number.getRawInput() : "";
}
String formattedNumber = "";
// Clear the extension, as that part cannot normally be dialed together with the main number.
PhoneNumber numberNoExt = new PhoneNumber().mergeFrom(number).clearExtension();
String regionCode = getRegionCodeForCountryCode(countryCallingCode);
PhoneNumberType numberType = getNumberType(numberNoExt);
boolean isValidNumber = (numberType != PhoneNumberType.UNKNOWN);
if (regionCallingFrom.equals(regionCode)) {
boolean isFixedLineOrMobile =
(numberType == PhoneNumberType.FIXED_LINE) || (numberType == PhoneNumberType.MOBILE)
|| (numberType == PhoneNumberType.FIXED_LINE_OR_MOBILE);
// Carrier codes may be needed in some countries. We handle this here.
if (regionCode.equals("BR") && isFixedLineOrMobile) {
// Historically, we set this to an empty string when parsing with raw input if none was
// found in the input string. However, this doesn't result in a number we can dial. For this
// reason, we treat the empty string the same as if it isn't set at all.
formattedNumber = numberNoExt.getPreferredDomesticCarrierCode().length() > 0
? formattedNumber = formatNationalNumberWithPreferredCarrierCode(numberNoExt, "")
// Brazilian fixed line and mobile numbers need to be dialed with a carrier code when
// called within Brazil. Without that, most of the carriers won't connect the call.
// Because of that, we return an empty string here.
: "";
} else if (countryCallingCode == NANPA_COUNTRY_CODE) {
// For NANPA countries, we output international format for numbers that can be dialed
// internationally, since that always works, except for numbers which might potentially be
// short numbers, which are always dialled in national format.
PhoneMetadata regionMetadata = getMetadataForRegion(regionCallingFrom);
if (canBeInternationallyDialled(numberNoExt)
&& testNumberLength(getNationalSignificantNumber(numberNoExt), regionMetadata)
!= ValidationResult.TOO_SHORT) {
formattedNumber = format(numberNoExt, PhoneNumberFormat.INTERNATIONAL);
} else {
formattedNumber = format(numberNoExt, PhoneNumberFormat.NATIONAL);
}
} else {
// For non-geographical countries, and Mexican, Chilean, and Uzbek fixed line and mobile
// numbers, we output international format for numbers that can be dialed internationally as
// that always works.
if ((regionCode.equals(REGION_CODE_FOR_NON_GEO_ENTITY)
// MX fixed line and mobile numbers should always be formatted in international format,
// even when dialed within MX. For national format to work, a carrier code needs to be
// used, and the correct carrier code depends on if the caller and callee are from the
// same local area. It is trickier to get that to work correctly than using
// international format, which is tested to work fine on all carriers.
// CL fixed line numbers need the national prefix when dialing in the national format,
// but don't have it when used for display. The reverse is true for mobile numbers. As
// a result, we output them in the international format to make it work.
// UZ mobile and fixed-line numbers have to be formatted in international format or
// prefixed with special codes like 03, 04 (for fixed-line) and 05 (for mobile) for
// dialling successfully from mobile devices. As we do not have complete information on
// special codes and to be consistent with formatting across all phone types we return
// the number in international format here.
|| ((regionCode.equals("MX") || regionCode.equals("CL")
|| regionCode.equals("UZ")) && isFixedLineOrMobile))
&& canBeInternationallyDialled(numberNoExt)) {
formattedNumber = format(numberNoExt, PhoneNumberFormat.INTERNATIONAL);
} else {
formattedNumber = format(numberNoExt, PhoneNumberFormat.NATIONAL);
}
}
} else if (isValidNumber && canBeInternationallyDialled(numberNoExt)) {
// We assume that short numbers are not diallable from outside their region, so if a number
// is not a valid regular length phone number, we treat it as if it cannot be internationally
// dialled.
return withFormatting ? format(numberNoExt, PhoneNumberFormat.INTERNATIONAL)
: format(numberNoExt, PhoneNumberFormat.E164);
}
return withFormatting ? formattedNumber
: normalizeDiallableCharsOnly(formattedNumber);
}
/**
* Formats a phone number for out-of-country dialing purposes. If no regionCallingFrom is
* supplied, we format the number in its INTERNATIONAL format. If the country calling code is the
* same as that of the region where the number is from, then NATIONAL formatting will be applied.
*
* If the number itself has a country calling code of zero or an otherwise invalid country
* calling code, then we return the number with no formatting applied.
*
* Note this function takes care of the case for calling inside of NANPA and between Russia and
* Kazakhstan (who share the same country calling code). In those cases, no international prefix
* is used. For regions which have multiple international prefixes, the number in its
* INTERNATIONAL format will be returned instead.
*
* @param number the phone number to be formatted
* @param regionCallingFrom the region where the call is being placed
* @return the formatted phone number
*/
public String formatOutOfCountryCallingNumber(PhoneNumber number,
String regionCallingFrom) {
if (!isValidRegionCode(regionCallingFrom)) {
logger.log(Level.WARNING,
"Trying to format number from invalid region "
+ regionCallingFrom
+ ". International formatting applied.");
return format(number, PhoneNumberFormat.INTERNATIONAL);
}
int countryCallingCode = number.getCountryCode();
String nationalSignificantNumber = getNationalSignificantNumber(number);
if (!hasValidCountryCallingCode(countryCallingCode)) {
return nationalSignificantNumber;
}
if (countryCallingCode == NANPA_COUNTRY_CODE) {
if (isNANPACountry(regionCallingFrom)) {
// For NANPA regions, return the national format for these regions but prefix it with the
// country calling code.
return countryCallingCode + " " + format(number, PhoneNumberFormat.NATIONAL);
}
} else if (countryCallingCode == getCountryCodeForValidRegion(regionCallingFrom)) {
// If regions share a country calling code, the country calling code need not be dialled.
// This also applies when dialling within a region, so this if clause covers both these cases.
// Technically this is the case for dialling from La Reunion to other overseas departments of
// France (French Guiana, Martinique, Guadeloupe), but not vice versa - so we don't cover this
// edge case for now and for those cases return the version including country calling code.
// Details here: http://www.petitfute.com/voyage/225-info-pratiques-reunion
return format(number, PhoneNumberFormat.NATIONAL);
}
// Metadata cannot be null because we checked 'isValidRegionCode()' above.
PhoneMetadata metadataForRegionCallingFrom = getMetadataForRegion(regionCallingFrom);
String internationalPrefix = metadataForRegionCallingFrom.getInternationalPrefix();
// In general, if there is a preferred international prefix, use that. Otherwise, for regions
// that have multiple international prefixes, the international format of the number is
// returned since we would not know which one to use.
String internationalPrefixForFormatting = "";
if (metadataForRegionCallingFrom.hasPreferredInternationalPrefix()) {
internationalPrefixForFormatting =
metadataForRegionCallingFrom.getPreferredInternationalPrefix();
} else if (SINGLE_INTERNATIONAL_PREFIX.matcher(internationalPrefix).matches()) {
internationalPrefixForFormatting = internationalPrefix;
}
String regionCode = getRegionCodeForCountryCode(countryCallingCode);
// Metadata cannot be null because the country calling code is valid.
PhoneMetadata metadataForRegion =
getMetadataForRegionOrCallingCode(countryCallingCode, regionCode);
String formattedNationalNumber =
formatNsn(nationalSignificantNumber, metadataForRegion, PhoneNumberFormat.INTERNATIONAL);
StringBuilder formattedNumber = new StringBuilder(formattedNationalNumber);
maybeAppendFormattedExtension(number, metadataForRegion, PhoneNumberFormat.INTERNATIONAL,
formattedNumber);
if (internationalPrefixForFormatting.length() > 0) {
formattedNumber.insert(0, " ").insert(0, countryCallingCode).insert(0, " ")
.insert(0, internationalPrefixForFormatting);
} else {
prefixNumberWithCountryCallingCode(countryCallingCode,
PhoneNumberFormat.INTERNATIONAL,
formattedNumber);
}
return formattedNumber.toString();
}
/**
* Formats a phone number using the original phone number format (e.g. INTERNATIONAL or NATIONAL)
* that the number is parsed from, provided that the number has been parsed with {@link
* parseAndKeepRawInput}. Otherwise the number will be formatted in NATIONAL format.
*
* The original format is embedded in the country_code_source field of the PhoneNumber object
* passed in, which is only set when parsing keeps the raw input. When we don't have a formatting
* pattern for the number, the method falls back to returning the raw input.
*
* Note this method guarantees no digit will be inserted, removed or modified as a result of
* formatting.
*
* @param number the phone number that needs to be formatted in its original number format
* @param regionCallingFrom the region whose IDD needs to be prefixed if the original number has
* one
* @return the formatted phone number in its original number format
*/
@android.compat.annotation.UnsupportedAppUsage
public String formatInOriginalFormat(PhoneNumber number, String regionCallingFrom) {
if (number.hasRawInput() && !hasFormattingPatternForNumber(number)) {
// We check if we have the formatting pattern because without that, we might format the number
// as a group without national prefix.
return number.getRawInput();
}
if (!number.hasCountryCodeSource()) {
return format(number, PhoneNumberFormat.NATIONAL);
}
String formattedNumber;
switch (number.getCountryCodeSource()) {
case FROM_NUMBER_WITH_PLUS_SIGN:
formattedNumber = format(number, PhoneNumberFormat.INTERNATIONAL);
break;
case FROM_NUMBER_WITH_IDD:
formattedNumber = formatOutOfCountryCallingNumber(number, regionCallingFrom);
break;
case FROM_NUMBER_WITHOUT_PLUS_SIGN:
formattedNumber = format(number, PhoneNumberFormat.INTERNATIONAL).substring(1);
break;
case FROM_DEFAULT_COUNTRY:
// Fall-through to default case.
default:
String regionCode = getRegionCodeForCountryCode(number.getCountryCode());
// We strip non-digits from the NDD here, and from the raw input later, so that we can
// compare them easily.
String nationalPrefix = getNddPrefixForRegion(regionCode, true /* strip non-digits */);
String nationalFormat = format(number, PhoneNumberFormat.NATIONAL);
if (nationalPrefix == null || nationalPrefix.length() == 0) {
// If the region doesn't have a national prefix at all, we can safely return the national
// format without worrying about a national prefix being added.
formattedNumber = nationalFormat;
break;
}
// Otherwise, we check if the original number was entered with a national prefix.
if (rawInputContainsNationalPrefix(
number.getRawInput(), nationalPrefix, regionCode)) {
// If so, we can safely return the national format.
formattedNumber = nationalFormat;
break;
}
// Metadata cannot be null here because getNddPrefixForRegion() (above) returns null if
// there is no metadata for the region.
PhoneMetadata metadata = getMetadataForRegion(regionCode);
String nationalNumber = getNationalSignificantNumber(number);
NumberFormat formatRule =
chooseFormattingPatternForNumber(metadata.getNumberFormatList(), nationalNumber);
// The format rule could still be null here if the national number was 0 and there was no
// raw input (this should not be possible for numbers generated by the phonenumber library
// as they would also not have a country calling code and we would have exited earlier).
if (formatRule == null) {
formattedNumber = nationalFormat;
break;
}
// When the format we apply to this number doesn't contain national prefix, we can just
// return the national format.
// TODO: Refactor the code below with the code in
// isNationalPrefixPresentIfRequired.
String candidateNationalPrefixRule = formatRule.getNationalPrefixFormattingRule();
// We assume that the first-group symbol will never be _before_ the national prefix.
int indexOfFirstGroup = candidateNationalPrefixRule.indexOf("$1");
if (indexOfFirstGroup <= 0) {
formattedNumber = nationalFormat;
break;
}
candidateNationalPrefixRule =
candidateNationalPrefixRule.substring(0, indexOfFirstGroup);
candidateNationalPrefixRule = normalizeDigitsOnly(candidateNationalPrefixRule);
if (candidateNationalPrefixRule.length() == 0) {
// National prefix not used when formatting this number.
formattedNumber = nationalFormat;
break;
}
// Otherwise, we need to remove the national prefix from our output.
NumberFormat.Builder numFormatCopy = NumberFormat.newBuilder();
numFormatCopy.mergeFrom(formatRule);
numFormatCopy.clearNationalPrefixFormattingRule();
List Caveats: Warning: Do not use this method for do-your-own formatting - for some regions, the
* national dialling prefix is used only for certain types of numbers. Use the library's
* formatting functions to prefix the national prefix when required.
*
* @param regionCode the region that we want to get the dialling prefix for
* @param stripNonDigits true to strip non-digits from the national dialling prefix
* @return the dialling prefix for the region denoted by regionCode
*/
public String getNddPrefixForRegion(String regionCode, boolean stripNonDigits) {
PhoneMetadata metadata = getMetadataForRegion(regionCode);
if (metadata == null) {
logger.log(Level.WARNING,
"Invalid or missing region code ("
+ ((regionCode == null) ? "null" : regionCode)
+ ") provided.");
return null;
}
String nationalPrefix = metadata.getNationalPrefix();
// If no national prefix was found, we return null.
if (nationalPrefix.length() == 0) {
return null;
}
if (stripNonDigits) {
// Note: if any other non-numeric symbols are ever used in national prefixes, these would have
// to be removed here as well.
nationalPrefix = nationalPrefix.replace("~", "");
}
return nationalPrefix;
}
/**
* Checks if this is a region under the North American Numbering Plan Administration (NANPA).
*
* @return true if regionCode is one of the regions under NANPA
*/
public boolean isNANPACountry(String regionCode) {
return nanpaRegions.contains(regionCode);
}
/**
* Checks if the number is a valid vanity (alpha) number such as 800 MICROSOFT. A valid vanity
* number will start with at least 3 digits and will have three or more alpha characters. This
* does not do region-specific checks - to work out if this number is actually valid for a region,
* it should be parsed and methods such as {@link #isPossibleNumberWithReason} and
* {@link #isValidNumber} should be used.
*
* @param number the number that needs to be checked
* @return true if the number is a valid vanity number
*/
public boolean isAlphaNumber(CharSequence number) {
if (!isViablePhoneNumber(number)) {
// Number is too short, or doesn't match the basic phone number pattern.
return false;
}
StringBuilder strippedNumber = new StringBuilder(number);
maybeStripExtension(strippedNumber);
return VALID_ALPHA_PHONE_PATTERN.matcher(strippedNumber).matches();
}
/**
* Convenience wrapper around {@link #isPossibleNumberWithReason}. Instead of returning the reason
* for failure, this method returns true if the number is either a possible fully-qualified number
* (containing the area code and country code), or if the number could be a possible local number
* (with a country code, but missing an area code). Local numbers are considered possible if they
* could be possibly dialled in this format: if the area code is needed for a call to connect, the
* number is not considered possible without it.
*
* @param number the number that needs to be checked
* @return true if the number is possible
*/
@android.compat.annotation.UnsupportedAppUsage
public boolean isPossibleNumber(PhoneNumber number) {
ValidationResult result = isPossibleNumberWithReason(number);
return result == ValidationResult.IS_POSSIBLE
|| result == ValidationResult.IS_POSSIBLE_LOCAL_ONLY;
}
/**
* Convenience wrapper around {@link #isPossibleNumberForTypeWithReason}. Instead of returning the
* reason for failure, this method returns true if the number is either a possible fully-qualified
* number (containing the area code and country code), or if the number could be a possible local
* number (with a country code, but missing an area code). Local numbers are considered possible
* if they could be possibly dialled in this format: if the area code is needed for a call to
* connect, the number is not considered possible without it.
*
* @param number the number that needs to be checked
* @param type the type we are interested in
* @return true if the number is possible for this particular type
*/
public boolean isPossibleNumberForType(PhoneNumber number, PhoneNumberType type) {
ValidationResult result = isPossibleNumberForTypeWithReason(number, type);
return result == ValidationResult.IS_POSSIBLE
|| result == ValidationResult.IS_POSSIBLE_LOCAL_ONLY;
}
/**
* Helper method to check a number against possible lengths for this region, based on the metadata
* being passed in, and determine whether it matches, or is too short or too long.
*/
private ValidationResult testNumberLength(CharSequence number, PhoneMetadata metadata) {
return testNumberLength(number, metadata, PhoneNumberType.UNKNOWN);
}
/**
* Helper method to check a number against possible lengths for this number type, and determine
* whether it matches, or is too short or too long.
*/
private ValidationResult testNumberLength(
CharSequence number, PhoneMetadata metadata, PhoneNumberType type) {
PhoneNumberDesc descForType = getNumberDescByType(metadata, type);
// There should always be "possibleLengths" set for every element. This is declared in the XML
// schema which is verified by PhoneNumberMetadataSchemaTest.
// For size efficiency, where a sub-description (e.g. fixed-line) has the same possibleLengths
// as the parent, this is missing, so we fall back to the general desc (where no numbers of the
// type exist at all, there is one possible length (-1) which is guaranteed not to match the
// length of any real phone number).
List This method first parses the number, then invokes {@link #isPossibleNumber(PhoneNumber)}
* with the resultant PhoneNumber object.
*
* @param number the number that needs to be checked
* @param regionDialingFrom the region that we are expecting the number to be dialed from.
* Note this is different from the region where the number belongs. For example, the number
* +1 650 253 0000 is a number that belongs to US. When written in this form, it can be
* dialed from any region. When it is written as 00 1 650 253 0000, it can be dialed from any
* region which uses an international dialling prefix of 00. When it is written as
* 650 253 0000, it can only be dialed from within the US, and when written as 253 0000, it
* can only be dialed from within a smaller area in the US (Mountain View, CA, to be more
* specific).
* @return true if the number is possible
*/
public boolean isPossibleNumber(CharSequence number, String regionDialingFrom) {
try {
return isPossibleNumber(parse(number, regionDialingFrom));
} catch (NumberParseException e) {
return false;
}
}
/**
* Attempts to extract a valid number from a phone number that is too long to be valid, and resets
* the PhoneNumber object passed in to that valid version. If no valid number could be extracted,
* the PhoneNumber object passed in will not be modified.
* @param number a PhoneNumber object which contains a number that is too long to be valid
* @return true if a valid phone number can be successfully extracted
*/
public boolean truncateTooLongNumber(PhoneNumber number) {
if (isValidNumber(number)) {
return true;
}
PhoneNumber numberCopy = new PhoneNumber();
numberCopy.mergeFrom(number);
long nationalNumber = number.getNationalNumber();
do {
nationalNumber /= 10;
numberCopy.setNationalNumber(nationalNumber);
if (isPossibleNumberWithReason(numberCopy) == ValidationResult.TOO_SHORT
|| nationalNumber == 0) {
return false;
}
} while (!isValidNumber(numberCopy));
number.setNationalNumber(nationalNumber);
return true;
}
/**
* Gets an {@link com.android.i18n.phonenumbers.AsYouTypeFormatter} for the specific region.
*
* @param regionCode the region where the phone number is being entered
* @return an {@link com.android.i18n.phonenumbers.AsYouTypeFormatter} object, which can be used
* to format phone numbers in the specific region "as you type"
*/
@android.compat.annotation.UnsupportedAppUsage
public AsYouTypeFormatter getAsYouTypeFormatter(String regionCode) {
return new AsYouTypeFormatter(regionCode);
}
// Extracts country calling code from fullNumber, returns it and places the remaining number in
// nationalNumber. It assumes that the leading plus sign or IDD has already been removed. Returns
// 0 if fullNumber doesn't start with a valid country calling code, and leaves nationalNumber
// unmodified.
int extractCountryCode(StringBuilder fullNumber, StringBuilder nationalNumber) {
if ((fullNumber.length() == 0) || (fullNumber.charAt(0) == '0')) {
// Country codes do not begin with a '0'.
return 0;
}
int potentialCountryCode;
int numberLength = fullNumber.length();
for (int i = 1; i <= MAX_LENGTH_COUNTRY_CODE && i <= numberLength; i++) {
potentialCountryCode = Integer.parseInt(fullNumber.substring(0, i));
if (countryCallingCodeToRegionCodeMap.containsKey(potentialCountryCode)) {
nationalNumber.append(fullNumber.substring(i));
return potentialCountryCode;
}
}
return 0;
}
/**
* Tries to extract a country calling code from a number. This method will return zero if no
* country calling code is considered to be present. Country calling codes are extracted in the
* following ways:
* This method will throw a {@link com.android.i18n.phonenumbers.NumberParseException} if the
* number is not considered to be a possible number. Note that validation of whether the number
* is actually a valid number for a particular region is not performed. This can be done
* separately with {@link #isValidNumber}.
*
* Note this method canonicalizes the phone number such that different representations can be
* easily compared, no matter what form it was originally entered in (e.g. national,
* international). If you want to record context about the number being parsed, such as the raw
* input that was entered, how the country code was derived etc. then call {@link
* #parseAndKeepRawInput} instead.
*
* @param numberToParse number that we are attempting to parse. This can contain formatting such
* as +, ( and -, as well as a phone number extension. It can also be provided in RFC3966
* format.
* @param defaultRegion region that we are expecting the number to be from. This is only used if
* the number being parsed is not written in international format. The country_code for the
* number in this case would be stored as that of the default region supplied. If the number
* is guaranteed to start with a '+' followed by the country calling code, then RegionCode.ZZ
* or null can be supplied.
* @return a phone number proto buffer filled with the parsed number
* @throws NumberParseException if the string is not considered to be a viable phone number (e.g.
* too few or too many digits) or if no default region was supplied and the number is not in
* international format (does not start with +)
*/
public PhoneNumber parse(CharSequence numberToParse, String defaultRegion)
throws NumberParseException {
PhoneNumber phoneNumber = new PhoneNumber();
parse(numberToParse, defaultRegion, phoneNumber);
return phoneNumber;
}
/**
* Same as {@link #parse(CharSequence, String)}, but accepts mutable PhoneNumber as a
* parameter to decrease object creation when invoked many times.
*/
public void parse(CharSequence numberToParse, String defaultRegion, PhoneNumber phoneNumber)
throws NumberParseException {
parseHelper(numberToParse, defaultRegion, false, true, phoneNumber);
}
/**
* Parses a string and returns it in proto buffer format. This method differs from {@link #parse}
* in that it always populates the raw_input field of the protocol buffer with numberToParse as
* well as the country_code_source field.
*
* @param numberToParse number that we are attempting to parse. This can contain formatting such
* as +, ( and -, as well as a phone number extension.
* @param defaultRegion region that we are expecting the number to be from. This is only used if
* the number being parsed is not written in international format. The country calling code
* for the number in this case would be stored as that of the default region supplied.
* @return a phone number proto buffer filled with the parsed number
* @throws NumberParseException if the string is not considered to be a viable phone number or if
* no default region was supplied
*/
public PhoneNumber parseAndKeepRawInput(CharSequence numberToParse, String defaultRegion)
throws NumberParseException {
PhoneNumber phoneNumber = new PhoneNumber();
parseAndKeepRawInput(numberToParse, defaultRegion, phoneNumber);
return phoneNumber;
}
/**
* Same as{@link #parseAndKeepRawInput(CharSequence, String)}, but accepts a mutable
* PhoneNumber as a parameter to decrease object creation when invoked many times.
*/
public void parseAndKeepRawInput(CharSequence numberToParse, String defaultRegion,
PhoneNumber phoneNumber)
throws NumberParseException {
parseHelper(numberToParse, defaultRegion, true, true, phoneNumber);
}
/**
* Returns an iterable over all {@link PhoneNumberMatch PhoneNumberMatches} in {@code text}. This
* is a shortcut for {@link #findNumbers(CharSequence, String, Leniency, long)
* getMatcher(text, defaultRegion, Leniency.VALID, Long.MAX_VALUE)}.
*
* @param text the text to search for phone numbers, null for no text
* @param defaultRegion region that we are expecting the number to be from. This is only used if
* the number being parsed is not written in international format. The country_code for the
* number in this case would be stored as that of the default region supplied. May be null if
* only international numbers are expected.
*/
public Iterable Returns EXACT_MATCH if the country_code, NSN, presence of a leading zero for Italian numbers
* and any extension present are the same.
* Returns NSN_MATCH if either or both has no region specified, and the NSNs and extensions are
* the same.
* Returns SHORT_NSN_MATCH if either or both has no region specified, or the region specified is
* the same, and one NSN could be a shorter version of the other number. This includes the case
* where one has an extension specified, and the other does not.
* Returns NO_MATCH otherwise.
* For example, the numbers +1 345 657 1234 and 657 1234 are a SHORT_NSN_MATCH.
* The numbers +1 345 657 1234 and 345 657 are a NO_MATCH.
*
* @param firstNumberIn first number to compare
* @param secondNumberIn second number to compare
*
* @return NO_MATCH, SHORT_NSN_MATCH, NSN_MATCH or EXACT_MATCH depending on the level of equality
* of the two numbers, described in the method definition.
*/
@android.compat.annotation.UnsupportedAppUsage
public MatchType isNumberMatch(PhoneNumber firstNumberIn, PhoneNumber secondNumberIn) {
// We only care about the fields that uniquely define a number, so we copy these across
// explicitly.
PhoneNumber firstNumber = copyCoreFieldsOnly(firstNumberIn);
PhoneNumber secondNumber = copyCoreFieldsOnly(secondNumberIn);
// Early exit if both had extensions and these are different.
if (firstNumber.hasExtension() && secondNumber.hasExtension()
&& !firstNumber.getExtension().equals(secondNumber.getExtension())) {
return MatchType.NO_MATCH;
}
int firstNumberCountryCode = firstNumber.getCountryCode();
int secondNumberCountryCode = secondNumber.getCountryCode();
// Both had country_code specified.
if (firstNumberCountryCode != 0 && secondNumberCountryCode != 0) {
if (firstNumber.exactlySameAs(secondNumber)) {
return MatchType.EXACT_MATCH;
} else if (firstNumberCountryCode == secondNumberCountryCode
&& isNationalNumberSuffixOfTheOther(firstNumber, secondNumber)) {
// A SHORT_NSN_MATCH occurs if there is a difference because of the presence or absence of
// an 'Italian leading zero', the presence or absence of an extension, or one NSN being a
// shorter variant of the other.
return MatchType.SHORT_NSN_MATCH;
}
// This is not a match.
return MatchType.NO_MATCH;
}
// Checks cases where one or both country_code fields were not specified. To make equality
// checks easier, we first set the country_code fields to be equal.
firstNumber.setCountryCode(secondNumberCountryCode);
// If all else was the same, then this is an NSN_MATCH.
if (firstNumber.exactlySameAs(secondNumber)) {
return MatchType.NSN_MATCH;
}
if (isNationalNumberSuffixOfTheOther(firstNumber, secondNumber)) {
return MatchType.SHORT_NSN_MATCH;
}
return MatchType.NO_MATCH;
}
// Returns true when one national number is the suffix of the other or both are the same.
private boolean isNationalNumberSuffixOfTheOther(PhoneNumber firstNumber,
PhoneNumber secondNumber) {
String firstNumberNationalNumber = String.valueOf(firstNumber.getNationalNumber());
String secondNumberNationalNumber = String.valueOf(secondNumber.getNationalNumber());
// Note that endsWith returns true if the numbers are equal.
return firstNumberNationalNumber.endsWith(secondNumberNationalNumber)
|| secondNumberNationalNumber.endsWith(firstNumberNationalNumber);
}
/**
* Takes two phone numbers as strings and compares them for equality. This is a convenience
* wrapper for {@link #isNumberMatch(PhoneNumber, PhoneNumber)}. No default region is known.
*
* @param firstNumber first number to compare. Can contain formatting, and can have country
* calling code specified with + at the start.
* @param secondNumber second number to compare. Can contain formatting, and can have country
* calling code specified with + at the start.
* @return NOT_A_NUMBER, NO_MATCH, SHORT_NSN_MATCH, NSN_MATCH, EXACT_MATCH. See
* {@link #isNumberMatch(PhoneNumber, PhoneNumber)} for more details.
*/
public MatchType isNumberMatch(CharSequence firstNumber, CharSequence secondNumber) {
try {
PhoneNumber firstNumberAsProto = parse(firstNumber, UNKNOWN_REGION);
return isNumberMatch(firstNumberAsProto, secondNumber);
} catch (NumberParseException e) {
if (e.getErrorType() == NumberParseException.ErrorType.INVALID_COUNTRY_CODE) {
try {
PhoneNumber secondNumberAsProto = parse(secondNumber, UNKNOWN_REGION);
return isNumberMatch(secondNumberAsProto, firstNumber);
} catch (NumberParseException e2) {
if (e2.getErrorType() == NumberParseException.ErrorType.INVALID_COUNTRY_CODE) {
try {
PhoneNumber firstNumberProto = new PhoneNumber();
PhoneNumber secondNumberProto = new PhoneNumber();
parseHelper(firstNumber, null, false, false, firstNumberProto);
parseHelper(secondNumber, null, false, false, secondNumberProto);
return isNumberMatch(firstNumberProto, secondNumberProto);
} catch (NumberParseException e3) {
// Fall through and return MatchType.NOT_A_NUMBER.
}
}
}
}
}
// One or more of the phone numbers we are trying to match is not a viable phone number.
return MatchType.NOT_A_NUMBER;
}
/**
* Takes two phone numbers and compares them for equality. This is a convenience wrapper for
* {@link #isNumberMatch(PhoneNumber, PhoneNumber)}. No default region is known.
*
* @param firstNumber first number to compare in proto buffer format
* @param secondNumber second number to compare. Can contain formatting, and can have country
* calling code specified with + at the start.
* @return NOT_A_NUMBER, NO_MATCH, SHORT_NSN_MATCH, NSN_MATCH, EXACT_MATCH. See
* {@link #isNumberMatch(PhoneNumber, PhoneNumber)} for more details.
*/
public MatchType isNumberMatch(PhoneNumber firstNumber, CharSequence secondNumber) {
// First see if the second number has an implicit country calling code, by attempting to parse
// it.
try {
PhoneNumber secondNumberAsProto = parse(secondNumber, UNKNOWN_REGION);
return isNumberMatch(firstNumber, secondNumberAsProto);
} catch (NumberParseException e) {
if (e.getErrorType() == NumberParseException.ErrorType.INVALID_COUNTRY_CODE) {
// The second number has no country calling code. EXACT_MATCH is no longer possible.
// We parse it as if the region was the same as that for the first number, and if
// EXACT_MATCH is returned, we replace this with NSN_MATCH.
String firstNumberRegion = getRegionCodeForCountryCode(firstNumber.getCountryCode());
try {
if (!firstNumberRegion.equals(UNKNOWN_REGION)) {
PhoneNumber secondNumberWithFirstNumberRegion = parse(secondNumber, firstNumberRegion);
MatchType match = isNumberMatch(firstNumber, secondNumberWithFirstNumberRegion);
if (match == MatchType.EXACT_MATCH) {
return MatchType.NSN_MATCH;
}
return match;
} else {
// If the first number didn't have a valid country calling code, then we parse the
// second number without one as well.
PhoneNumber secondNumberProto = new PhoneNumber();
parseHelper(secondNumber, null, false, false, secondNumberProto);
return isNumberMatch(firstNumber, secondNumberProto);
}
} catch (NumberParseException e2) {
// Fall-through to return NOT_A_NUMBER.
}
}
}
// One or more of the phone numbers we are trying to match is not a viable phone number.
return MatchType.NOT_A_NUMBER;
}
/**
* Returns true if the number can be dialled from outside the region, or unknown. If the number
* can only be dialled from within the region, returns false. Does not check the number is a valid
* number. Note that, at the moment, this method does not handle short numbers (which are
* currently all presumed to not be diallable from outside their country).
*
* @param number the phone-number for which we want to know whether it is diallable from
* outside the region
*/
public boolean canBeInternationallyDialled(PhoneNumber number) {
PhoneMetadata metadata = getMetadataForRegion(getRegionCodeForNumber(number));
if (metadata == null) {
// Note numbers belonging to non-geographical entities (e.g. +800 numbers) are always
// internationally diallable, and will be caught here.
return true;
}
String nationalSignificantNumber = getNationalSignificantNumber(number);
return !isNumberMatchingDesc(nationalSignificantNumber, metadata.getNoInternationalDialling());
}
/**
* Returns true if the supplied region supports mobile number portability. Returns false for
* invalid, unknown or regions that don't support mobile number portability.
*
* @param regionCode the region for which we want to know whether it supports mobile number
* portability or not
*/
public boolean isMobileNumberPortableRegion(String regionCode) {
PhoneMetadata metadata = getMetadataForRegion(regionCode);
if (metadata == null) {
logger.log(Level.WARNING, "Invalid or unknown region code provided: " + regionCode);
return false;
}
return metadata.getMobileNumberPortableRegion();
}
}
{@code
* PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
* PhoneNumber number = phoneUtil.parse("16502530000", "US");
* String nationalSignificantNumber = phoneUtil.getNationalSignificantNumber(number);
* String areaCode;
* String subscriberNumber;
*
* int areaCodeLength = phoneUtil.getLengthOfGeographicalAreaCode(number);
* if (areaCodeLength > 0) {
* areaCode = nationalSignificantNumber.substring(0, areaCodeLength);
* subscriberNumber = nationalSignificantNumber.substring(areaCodeLength);
* } else {
* areaCode = "";
* subscriberNumber = nationalSignificantNumber;
* }
* }
*
* N.B.: area code is a very ambiguous concept, so the I18N team generally recommends against
* using it for most purposes, but recommends using the more general {@code national_number}
* instead. Read the following carefully before deciding to use this method:
*
*
* @param number the PhoneNumber object for which clients
* want to know the length of the area code
* @return the length of area code of the PhoneNumber object
* passed in
*/
public int getLengthOfGeographicalAreaCode(PhoneNumber number) {
PhoneMetadata metadata = getMetadataForRegion(getRegionCodeForNumber(number));
if (metadata == null) {
return 0;
}
// If a country doesn't use a national prefix, and this number doesn't have an Italian leading
// zero, we assume it is a closed dialling plan with no area codes.
if (!metadata.hasNationalPrefix() && !number.isItalianLeadingZero()) {
return 0;
}
PhoneNumberType type = getNumberType(number);
int countryCallingCode = number.getCountryCode();
if (type == PhoneNumberType.MOBILE
// Note this is a rough heuristic; it doesn't cover Indonesia well, for example, where area
// codes are present for some mobile phones but not for others. We have no better way of
// representing this in the metadata at this point.
&& GEO_MOBILE_COUNTRIES_WITHOUT_MOBILE_AREA_CODES.contains(countryCallingCode)) {
return 0;
}
if (!isNumberGeographical(type, countryCallingCode)) {
return 0;
}
return getLengthOfNationalDestinationCode(number);
}
/**
* Gets the length of the national destination code (NDC) from the
* PhoneNumber object passed in, so that clients could use it
* to split a national significant number into NDC and subscriber number. The NDC of a phone
* number is normally the first group of digit(s) right after the country calling code when the
* number is formatted in the international format, if there is a subscriber number part that
* follows.
*
* N.B.: similar to an area code, not all numbers have an NDC!
*
* An example of how this could be used:
*
* {@code
* PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
* PhoneNumber number = phoneUtil.parse("18002530000", "US");
* String nationalSignificantNumber = phoneUtil.getNationalSignificantNumber(number);
* String nationalDestinationCode;
* String subscriberNumber;
*
* int nationalDestinationCodeLength = phoneUtil.getLengthOfNationalDestinationCode(number);
* if (nationalDestinationCodeLength > 0) {
* nationalDestinationCode = nationalSignificantNumber.substring(0,
* nationalDestinationCodeLength);
* subscriberNumber = nationalSignificantNumber.substring(nationalDestinationCodeLength);
* } else {
* nationalDestinationCode = "";
* subscriberNumber = nationalSignificantNumber;
* }
* }
*
* Refer to the unittests to see the difference between this function and
* {@link #getLengthOfGeographicalAreaCode}.
*
* @param number the PhoneNumber object for which clients
* want to know the length of the NDC
* @return the length of NDC of the PhoneNumber object
* passed in, which could be zero
*/
public int getLengthOfNationalDestinationCode(PhoneNumber number) {
PhoneNumber copiedProto;
if (number.hasExtension()) {
// We don't want to alter the proto given to us, but we don't want to include the extension
// when we format it, so we copy it and clear the extension here.
copiedProto = new PhoneNumber();
copiedProto.mergeFrom(number);
copiedProto.clearExtension();
} else {
copiedProto = number;
}
String nationalSignificantNumber = format(copiedProto,
PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL);
String[] numberGroups = NON_DIGITS_PATTERN.split(nationalSignificantNumber);
// The pattern will start with "+COUNTRY_CODE " so the first group will always be the empty
// string (before the + symbol) and the second group will be the country calling code. The third
// group will be area code if it is not the last group.
if (numberGroups.length <= 3) {
return 0;
}
if (getNumberType(number) == PhoneNumberType.MOBILE) {
// For example Argentinian mobile numbers, when formatted in the international format, are in
// the form of +54 9 NDC XXXX.... As a result, we take the length of the third group (NDC) and
// add the length of the second group (which is the mobile token), which also forms part of
// the national significant number. This assumes that the mobile token is always formatted
// separately from the rest of the phone number.
String mobileToken = getCountryMobileToken(number.getCountryCode());
if (!mobileToken.equals("")) {
return numberGroups[2].length() + numberGroups[3].length();
}
}
return numberGroups[2].length();
}
/**
* Returns the mobile token for the provided country calling code if it has one, otherwise
* returns an empty string. A mobile token is a number inserted before the area code when dialing
* a mobile number from that country from abroad.
*
* @param countryCallingCode the country calling code for which we want the mobile token
* @return the mobile token, as a string, for the given country calling code
*/
public static String getCountryMobileToken(int countryCallingCode) {
if (MOBILE_TOKEN_MAPPINGS.containsKey(countryCallingCode)) {
return MOBILE_TOKEN_MAPPINGS.get(countryCallingCode);
}
return "";
}
/**
* Normalizes a string of characters representing a phone number by replacing all characters found
* in the accompanying map with the values therein, and stripping all other characters if
* removeNonMatches is true.
*
* @param number a string of characters representing a phone number
* @param normalizationReplacements a mapping of characters to what they should be replaced by in
* the normalized version of the phone number
* @param removeNonMatches indicates whether characters that are not able to be replaced should
* be stripped from the number. If this is false, they will be left unchanged in the number.
* @return the normalized string version of the phone number
*/
private static String normalizeHelper(CharSequence number,
Map
*
*
* @param number the phone number that needs to be formatted
* @param regionCallingFrom the region where the call is being placed
* @return the formatted phone number
*/
public String formatOutOfCountryKeepingAlphaChars(PhoneNumber number,
String regionCallingFrom) {
String rawInput = number.getRawInput();
// If there is no raw input, then we can't keep alpha characters because there aren't any.
// In this case, we return formatOutOfCountryCallingNumber.
if (rawInput.length() == 0) {
return formatOutOfCountryCallingNumber(number, regionCallingFrom);
}
int countryCode = number.getCountryCode();
if (!hasValidCountryCallingCode(countryCode)) {
return rawInput;
}
// Strip any prefix such as country calling code, IDD, that was present. We do this by comparing
// the number in raw_input with the parsed number.
// To do this, first we normalize punctuation. We retain number grouping symbols such as " "
// only.
rawInput = normalizeHelper(rawInput, ALL_PLUS_NUMBER_GROUPING_SYMBOLS, true);
// Now we trim everything before the first three digits in the parsed number. We choose three
// because all valid alpha numbers have 3 digits at the start - if it does not, then we don't
// trim anything at all. Similarly, if the national number was less than three digits, we don't
// trim anything at all.
String nationalNumber = getNationalSignificantNumber(number);
if (nationalNumber.length() > 3) {
int firstNationalNumberDigit = rawInput.indexOf(nationalNumber.substring(0, 3));
if (firstNationalNumberDigit != -1) {
rawInput = rawInput.substring(firstNationalNumberDigit);
}
}
PhoneMetadata metadataForRegionCallingFrom = getMetadataForRegion(regionCallingFrom);
if (countryCode == NANPA_COUNTRY_CODE) {
if (isNANPACountry(regionCallingFrom)) {
return countryCode + " " + rawInput;
}
} else if (metadataForRegionCallingFrom != null
&& countryCode == getCountryCodeForValidRegion(regionCallingFrom)) {
NumberFormat formattingPattern =
chooseFormattingPatternForNumber(metadataForRegionCallingFrom.getNumberFormatList(),
nationalNumber);
if (formattingPattern == null) {
// If no pattern above is matched, we format the original input.
return rawInput;
}
NumberFormat.Builder newFormat = NumberFormat.newBuilder();
newFormat.mergeFrom(formattingPattern);
// The first group is the first group of digits that the user wrote together.
newFormat.setPattern("(\\d+)(.*)");
// Here we just concatenate them back together after the national prefix has been fixed.
newFormat.setFormat("$1$2");
// Now we format using this pattern instead of the default pattern, but with the national
// prefix prefixed if necessary.
// This will not work in the cases where the pattern (and not the leading digits) decide
// whether a national prefix needs to be used, since we have overridden the pattern to match
// anything, but that is not the case in the metadata to date.
return formatNsnUsingPattern(rawInput, newFormat.build(), PhoneNumberFormat.NATIONAL);
}
String internationalPrefixForFormatting = "";
// If an unsupported region-calling-from is entered, or a country with multiple international
// prefixes, the international format of the number is returned, unless there is a preferred
// international prefix.
if (metadataForRegionCallingFrom != null) {
String internationalPrefix = metadataForRegionCallingFrom.getInternationalPrefix();
internationalPrefixForFormatting =
SINGLE_INTERNATIONAL_PREFIX.matcher(internationalPrefix).matches()
? internationalPrefix
: metadataForRegionCallingFrom.getPreferredInternationalPrefix();
}
StringBuilder formattedNumber = new StringBuilder(rawInput);
String regionCode = getRegionCodeForCountryCode(countryCode);
// Metadata cannot be null because the country calling code is valid.
PhoneMetadata metadataForRegion = getMetadataForRegionOrCallingCode(countryCode, regionCode);
maybeAppendFormattedExtension(number, metadataForRegion,
PhoneNumberFormat.INTERNATIONAL, formattedNumber);
if (internationalPrefixForFormatting.length() > 0) {
formattedNumber.insert(0, " ").insert(0, countryCode).insert(0, " ")
.insert(0, internationalPrefixForFormatting);
} else {
// Invalid region entered as country-calling-from (so no metadata was found for it) or the
// region chosen has multiple international dialling prefixes.
if (!isValidRegionCode(regionCallingFrom)) {
logger.log(Level.WARNING,
"Trying to format number from invalid region "
+ regionCallingFrom
+ ". International formatting applied.");
}
prefixNumberWithCountryCallingCode(countryCode,
PhoneNumberFormat.INTERNATIONAL,
formattedNumber);
}
return formattedNumber.toString();
}
/**
* Gets the national significant number of a phone number. Note a national significant number
* doesn't contain a national prefix or any formatting.
*
* @param number the phone number for which the national significant number is needed
* @return the national significant number of the PhoneNumber object passed in
*/
@android.compat.annotation.UnsupportedAppUsage
public String getNationalSignificantNumber(PhoneNumber number) {
// If leading zero(s) have been set, we prefix this now. Note this is not a national prefix.
StringBuilder nationalNumber = new StringBuilder();
if (number.isItalianLeadingZero() && number.getNumberOfLeadingZeros() > 0) {
char[] zeros = new char[number.getNumberOfLeadingZeros()];
Arrays.fill(zeros, '0');
nationalNumber.append(new String(zeros));
}
nationalNumber.append(number.getNationalNumber());
return nationalNumber.toString();
}
/**
* A helper function that is used by format and formatByPattern.
*/
private void prefixNumberWithCountryCallingCode(int countryCallingCode,
PhoneNumberFormat numberFormat,
StringBuilder formattedNumber) {
switch (numberFormat) {
case E164:
formattedNumber.insert(0, countryCallingCode).insert(0, PLUS_SIGN);
return;
case INTERNATIONAL:
formattedNumber.insert(0, " ").insert(0, countryCallingCode).insert(0, PLUS_SIGN);
return;
case RFC3966:
formattedNumber.insert(0, "-").insert(0, countryCallingCode).insert(0, PLUS_SIGN)
.insert(0, RFC3966_PREFIX);
return;
case NATIONAL:
default:
return;
}
}
// Simple wrapper of formatNsn for the common case of no carrier code.
private String formatNsn(String number, PhoneMetadata metadata, PhoneNumberFormat numberFormat) {
return formatNsn(number, metadata, numberFormat, null);
}
// Note in some regions, the national number can be written in two completely different ways
// depending on whether it forms part of the NATIONAL format or INTERNATIONAL format. The
// numberFormat parameter here is used to specify which format to use for those cases. If a
// carrierCode is specified, this will be inserted into the formatted string to replace $CC.
private String formatNsn(String number,
PhoneMetadata metadata,
PhoneNumberFormat numberFormat,
CharSequence carrierCode) {
List
*
* @param number the number that needs to be checked
* @return a ValidationResult object which indicates whether the number is possible
*/
@android.compat.annotation.UnsupportedAppUsage
public ValidationResult isPossibleNumberWithReason(PhoneNumber number) {
return isPossibleNumberForTypeWithReason(number, PhoneNumberType.UNKNOWN);
}
/**
* Check whether a phone number is a possible number of a particular type. For types that don't
* exist in a particular region, this will return a result that isn't so useful; it is recommended
* that you use {@link #getSupportedTypesForRegion} or {@link #getSupportedTypesForNonGeoEntity}
* respectively before calling this method to determine whether you should call it for this number
* at all.
*
* This provides a more lenient check than {@link #isValidNumber} in the following sense:
*
*
*
*
* @param number the number that needs to be checked
* @param type the type we are interested in
* @return a ValidationResult object which indicates whether the number is possible
*/
public ValidationResult isPossibleNumberForTypeWithReason(
PhoneNumber number, PhoneNumberType type) {
String nationalNumber = getNationalSignificantNumber(number);
int countryCode = number.getCountryCode();
// Note: For regions that share a country calling code, like NANPA numbers, we just use the
// rules from the default region (US in this case) since the getRegionCodeForNumber will not
// work if the number is possible but not valid. There is in fact one country calling code (290)
// where the possible number pattern differs between various regions (Saint Helena and Tristan
// da Cuñha), but this is handled by putting all possible lengths for any country with this
// country calling code in the metadata for the default region in this case.
if (!hasValidCountryCallingCode(countryCode)) {
return ValidationResult.INVALID_COUNTRY_CODE;
}
String regionCode = getRegionCodeForCountryCode(countryCode);
// Metadata cannot be null because the country calling code is valid.
PhoneMetadata metadata = getMetadataForRegionOrCallingCode(countryCode, regionCode);
return testNumberLength(nationalNumber, metadata, type);
}
/**
* Check whether a phone number is a possible number given a number in the form of a string, and
* the region where the number could be dialed from. It provides a more lenient check than
* {@link #isValidNumber}. See {@link #isPossibleNumber(PhoneNumber)} for details.
*
*
*
* It will throw a NumberParseException if the number starts with a '+' but the country calling
* code supplied after this does not match that of any known region.
*
* @param number non-normalized telephone number that we wish to extract a country calling
* code from - may begin with '+'
* @param defaultRegionMetadata metadata about the region this number may be from
* @param nationalNumber a string buffer to store the national significant number in, in the case
* that a country calling code was extracted. The number is appended to any existing contents.
* If no country calling code was extracted, this will be left unchanged.
* @param keepRawInput true if the country_code_source and preferred_carrier_code fields of
* phoneNumber should be populated.
* @param phoneNumber the PhoneNumber object where the country_code and country_code_source need
* to be populated. Note the country_code is always populated, whereas country_code_source is
* only populated when keepCountryCodeSource is true.
* @return the country calling code extracted or 0 if none could be extracted
*/
// @VisibleForTesting
int maybeExtractCountryCode(CharSequence number, PhoneMetadata defaultRegionMetadata,
StringBuilder nationalNumber, boolean keepRawInput,
PhoneNumber phoneNumber)
throws NumberParseException {
if (number.length() == 0) {
return 0;
}
StringBuilder fullNumber = new StringBuilder(number);
// Set the default prefix to be something that will never match.
String possibleCountryIddPrefix = "NonMatch";
if (defaultRegionMetadata != null) {
possibleCountryIddPrefix = defaultRegionMetadata.getInternationalPrefix();
}
CountryCodeSource countryCodeSource =
maybeStripInternationalPrefixAndNormalize(fullNumber, possibleCountryIddPrefix);
if (keepRawInput) {
phoneNumber.setCountryCodeSource(countryCodeSource);
}
if (countryCodeSource != CountryCodeSource.FROM_DEFAULT_COUNTRY) {
if (fullNumber.length() <= MIN_LENGTH_FOR_NSN) {
throw new NumberParseException(NumberParseException.ErrorType.TOO_SHORT_AFTER_IDD,
"Phone number had an IDD, but after this was not "
+ "long enough to be a viable phone number.");
}
int potentialCountryCode = extractCountryCode(fullNumber, nationalNumber);
if (potentialCountryCode != 0) {
phoneNumber.setCountryCode(potentialCountryCode);
return potentialCountryCode;
}
// If this fails, they must be using a strange country calling code that we don't recognize,
// or that doesn't exist.
throw new NumberParseException(NumberParseException.ErrorType.INVALID_COUNTRY_CODE,
"Country calling code supplied was not recognised.");
} else if (defaultRegionMetadata != null) {
// Check to see if the number starts with the country calling code for the default region. If
// so, we remove the country calling code, and do some checks on the validity of the number
// before and after.
int defaultCountryCode = defaultRegionMetadata.getCountryCode();
String defaultCountryCodeString = String.valueOf(defaultCountryCode);
String normalizedNumber = fullNumber.toString();
if (normalizedNumber.startsWith(defaultCountryCodeString)) {
StringBuilder potentialNationalNumber =
new StringBuilder(normalizedNumber.substring(defaultCountryCodeString.length()));
PhoneNumberDesc generalDesc = defaultRegionMetadata.getGeneralDesc();
maybeStripNationalPrefixAndCarrierCode(
potentialNationalNumber, defaultRegionMetadata, null /* Don't need the carrier code */);
// If the number was not valid before but is valid now, or if it was too long before, we
// consider the number with the country calling code stripped to be a better result and
// keep that instead.
if ((!matcherApi.matchNationalNumber(fullNumber, generalDesc, false)
&& matcherApi.matchNationalNumber(potentialNationalNumber, generalDesc, false))
|| testNumberLength(fullNumber, defaultRegionMetadata) == ValidationResult.TOO_LONG) {
nationalNumber.append(potentialNationalNumber);
if (keepRawInput) {
phoneNumber.setCountryCodeSource(CountryCodeSource.FROM_NUMBER_WITHOUT_PLUS_SIGN);
}
phoneNumber.setCountryCode(defaultCountryCode);
return defaultCountryCode;
}
}
}
// No country calling code present.
phoneNumber.setCountryCode(0);
return 0;
}
/**
* Strips the IDD from the start of the number if present. Helper function used by
* maybeStripInternationalPrefixAndNormalize.
*/
private boolean parsePrefixAsIdd(Pattern iddPattern, StringBuilder number) {
Matcher m = iddPattern.matcher(number);
if (m.lookingAt()) {
int matchEnd = m.end();
// Only strip this if the first digit after the match is not a 0, since country calling codes
// cannot begin with 0.
Matcher digitMatcher = CAPTURING_DIGIT_PATTERN.matcher(number.substring(matchEnd));
if (digitMatcher.find()) {
String normalizedGroup = normalizeDigitsOnly(digitMatcher.group(1));
if (normalizedGroup.equals("0")) {
return false;
}
}
number.delete(0, matchEnd);
return true;
}
return false;
}
/**
* Strips any international prefix (such as +, 00, 011) present in the number provided, normalizes
* the resulting number, and indicates if an international prefix was present.
*
* @param number the non-normalized telephone number that we wish to strip any international
* dialing prefix from
* @param possibleIddPrefix the international direct dialing prefix from the region we
* think this number may be dialed in
* @return the corresponding CountryCodeSource if an international dialing prefix could be
* removed from the number, otherwise CountryCodeSource.FROM_DEFAULT_COUNTRY if the number did
* not seem to be in international format
*/
// @VisibleForTesting
CountryCodeSource maybeStripInternationalPrefixAndNormalize(
StringBuilder number,
String possibleIddPrefix) {
if (number.length() == 0) {
return CountryCodeSource.FROM_DEFAULT_COUNTRY;
}
// Check to see if the number begins with one or more plus signs.
Matcher m = PLUS_CHARS_PATTERN.matcher(number);
if (m.lookingAt()) {
number.delete(0, m.end());
// Can now normalize the rest of the number since we've consumed the "+" sign at the start.
normalize(number);
return CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN;
}
// Attempt to parse the first digits as an international prefix.
Pattern iddPattern = regexCache.getPatternForRegex(possibleIddPrefix);
normalize(number);
return parsePrefixAsIdd(iddPattern, number)
? CountryCodeSource.FROM_NUMBER_WITH_IDD
: CountryCodeSource.FROM_DEFAULT_COUNTRY;
}
/**
* Strips any national prefix (such as 0, 1) present in the number provided.
*
* @param number the normalized telephone number that we wish to strip any national
* dialing prefix from
* @param metadata the metadata for the region that we think this number is from
* @param carrierCode a place to insert the carrier code if one is extracted
* @return true if a national prefix or carrier code (or both) could be extracted
*/
// @VisibleForTesting
boolean maybeStripNationalPrefixAndCarrierCode(
StringBuilder number, PhoneMetadata metadata, StringBuilder carrierCode) {
int numberLength = number.length();
String possibleNationalPrefix = metadata.getNationalPrefixForParsing();
if (numberLength == 0 || possibleNationalPrefix.length() == 0) {
// Early return for numbers of zero length.
return false;
}
// Attempt to parse the first digits as a national prefix.
Matcher prefixMatcher = regexCache.getPatternForRegex(possibleNationalPrefix).matcher(number);
if (prefixMatcher.lookingAt()) {
PhoneNumberDesc generalDesc = metadata.getGeneralDesc();
// Check if the original number is viable.
boolean isViableOriginalNumber = matcherApi.matchNationalNumber(number, generalDesc, false);
// prefixMatcher.group(numOfGroups) == null implies nothing was captured by the capturing
// groups in possibleNationalPrefix; therefore, no transformation is necessary, and we just
// remove the national prefix.
int numOfGroups = prefixMatcher.groupCount();
String transformRule = metadata.getNationalPrefixTransformRule();
if (transformRule == null || transformRule.length() == 0
|| prefixMatcher.group(numOfGroups) == null) {
// If the original number was viable, and the resultant number is not, we return.
if (isViableOriginalNumber
&& !matcherApi.matchNationalNumber(
number.substring(prefixMatcher.end()), generalDesc, false)) {
return false;
}
if (carrierCode != null && numOfGroups > 0 && prefixMatcher.group(numOfGroups) != null) {
carrierCode.append(prefixMatcher.group(1));
}
number.delete(0, prefixMatcher.end());
return true;
} else {
// Check that the resultant number is still viable. If not, return. Check this by copying
// the string buffer and making the transformation on the copy first.
StringBuilder transformedNumber = new StringBuilder(number);
transformedNumber.replace(0, numberLength, prefixMatcher.replaceFirst(transformRule));
if (isViableOriginalNumber
&& !matcherApi.matchNationalNumber(transformedNumber.toString(), generalDesc, false)) {
return false;
}
if (carrierCode != null && numOfGroups > 1) {
carrierCode.append(prefixMatcher.group(1));
}
number.replace(0, number.length(), transformedNumber.toString());
return true;
}
}
return false;
}
/**
* Strips any extension (as in, the part of the number dialled after the call is connected,
* usually indicated with extn, ext, x or similar) from the end of the number, and returns it.
*
* @param number the non-normalized telephone number that we wish to strip the extension from
* @return the phone extension
*/
// @VisibleForTesting
String maybeStripExtension(StringBuilder number) {
Matcher m = EXTN_PATTERN.matcher(number);
// If we find a potential extension, and the number preceding this is a viable number, we assume
// it is an extension.
if (m.find() && isViablePhoneNumber(number.substring(0, m.start()))) {
// The numbers are captured into groups in the regular expression.
for (int i = 1, length = m.groupCount(); i <= length; i++) {
if (m.group(i) != null) {
// We go through the capturing groups until we find one that captured some digits. If none
// did, then we will return the empty string.
String extension = m.group(i);
number.delete(m.start(), number.length());
return extension;
}
}
}
return "";
}
/**
* Checks to see that the region code used is valid, or if it is not valid, that the number to
* parse starts with a + symbol so that we can attempt to infer the region from the number.
* Returns false if it cannot use the region provided and the region cannot be inferred.
*/
private boolean checkRegionForParsing(CharSequence numberToParse, String defaultRegion) {
if (!isValidRegionCode(defaultRegion)) {
// If the number is null or empty, we can't infer the region.
if ((numberToParse == null) || (numberToParse.length() == 0)
|| !PLUS_CHARS_PATTERN.matcher(numberToParse).lookingAt()) {
return false;
}
}
return true;
}
/**
* Parses a string and returns it as a phone number in proto buffer format. The method is quite
* lenient and looks for a number in the input text (raw input) and does not check whether the
* string is definitely only a phone number. To do this, it ignores punctuation and white-space,
* as well as any text before the number (e.g. a leading "Tel: ") and trims the non-number bits.
* It will accept a number in any format (E164, national, international etc), assuming it can be
* interpreted with the defaultRegion supplied. It also attempts to convert any alpha characters
* into digits if it thinks this is a vanity number of the type "1800 MICROSOFT".
*
*