/* * Copyright (C) 2011 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.google.i18n.phonenumbers.geocoding; import com.google.i18n.phonenumbers.NumberParseException; import com.google.i18n.phonenumbers.PhoneNumberUtil; import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberType; import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber; import com.google.i18n.phonenumbers.prefixmapper.PrefixFileReader; import java.util.List; import java.util.Locale; /** * An offline geocoder which provides geographical information related to a phone number. * * @author Shaopeng Jia */ public class PhoneNumberOfflineGeocoder { private static PhoneNumberOfflineGeocoder instance = null; private static final String MAPPING_DATA_DIRECTORY = "/com/google/i18n/phonenumbers/geocoding/data/"; private PrefixFileReader prefixFileReader = null; private final PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance(); // @VisibleForTesting PhoneNumberOfflineGeocoder(String phonePrefixDataDirectory) { prefixFileReader = new PrefixFileReader(phonePrefixDataDirectory); } /** * Gets a {@link PhoneNumberOfflineGeocoder} instance to carry out international phone number * geocoding. * *
The {@link PhoneNumberOfflineGeocoder} is implemented as a singleton. Therefore, calling
* this method multiple times will only result in one instance being created.
*
* @return a {@link PhoneNumberOfflineGeocoder} instance
*/
public static synchronized PhoneNumberOfflineGeocoder getInstance() {
if (instance == null) {
instance = new PhoneNumberOfflineGeocoder(MAPPING_DATA_DIRECTORY);
}
return instance;
}
/**
* Returns the customary display name in the given language for the given territory the phone
* number is from. If it could be from many territories, nothing is returned.
*/
private String getCountryNameForNumber(PhoneNumber number, Locale language) {
List This method assumes the validity of the number passed in has already been checked, and that
* the number is suitable for geocoding. We consider fixed-line and mobile numbers possible
* candidates for geocoding.
*
* @param number a valid phone number for which we want to get a text description
* @param languageCode the language code for which the description should be written
* @return a text description for the given language code for the given phone number
*/
public String getDescriptionForValidNumber(PhoneNumber number, Locale languageCode) {
String langStr = languageCode.getLanguage();
String scriptStr = ""; // No script is specified
String regionStr = languageCode.getCountry();
String areaDescription;
String mobileToken = PhoneNumberUtil.getCountryMobileToken(number.getCountryCode());
String nationalNumber = phoneUtil.getNationalSignificantNumber(number);
if (!mobileToken.equals("") && nationalNumber.startsWith(mobileToken)) {
// In some countries, eg. Argentina, mobile numbers have a mobile token before the national
// destination code, this should be removed before geocoding.
nationalNumber = nationalNumber.substring(mobileToken.length());
String region = phoneUtil.getRegionCodeForCountryCode(number.getCountryCode());
PhoneNumber copiedNumber;
try {
copiedNumber = phoneUtil.parse(nationalNumber, region);
} catch (NumberParseException e) {
// If this happens, just reuse what we had.
copiedNumber = number;
}
areaDescription = prefixFileReader.getDescriptionForNumber(copiedNumber, langStr, scriptStr,
regionStr);
} else {
areaDescription = prefixFileReader.getDescriptionForNumber(number, langStr, scriptStr,
regionStr);
}
return (areaDescription.length() > 0)
? areaDescription : getCountryNameForNumber(number, languageCode);
}
/**
* As per {@link #getDescriptionForValidNumber(PhoneNumber, Locale)} but also considers the
* region of the user. If the phone number is from the same region as the user, only a lower-level
* description will be returned, if one exists. Otherwise, the phone number's region will be
* returned, with optionally some more detailed information.
*
* For example, for a user from the region "US" (United States), we would show "Mountain View,
* CA" for a particular number, omitting the United States from the description. For a user from
* the United Kingdom (region "GB"), for the same number we may show "Mountain View, CA, United
* States" or even just "United States".
*
* This method assumes the validity of the number passed in has already been checked.
*
* @param number the phone number for which we want to get a text description
* @param languageCode the language code for which the description should be written
* @param userRegion the region code for a given user. This region will be omitted from the
* description if the phone number comes from this region. It is a two-letter uppercase ISO
* country code as defined by ISO 3166-1.
* @return a text description for the given language code for the given phone number, or empty
* string if the number passed in is invalid
*/
public String getDescriptionForValidNumber(PhoneNumber number, Locale languageCode,
String userRegion) {
// If the user region matches the number's region, then we just show the lower-level
// description, if one exists - if no description exists, we will show the region(country) name
// for the number.
String regionCode = phoneUtil.getRegionCodeForNumber(number);
if (userRegion.equals(regionCode)) {
return getDescriptionForValidNumber(number, languageCode);
}
// Otherwise, we just show the region(country) name for now.
return getRegionDisplayName(regionCode, languageCode);
// TODO: Concatenate the lower-level and country-name information in an appropriate
// way for each language.
}
/**
* As per {@link #getDescriptionForValidNumber(PhoneNumber, Locale)} but explicitly checks
* the validity of the number passed in.
*
* @param number the phone number for which we want to get a text description
* @param languageCode the language code for which the description should be written
* @return a text description for the given language code for the given phone number, or empty
* string if the number passed in is invalid
*/
public String getDescriptionForNumber(PhoneNumber number, Locale languageCode) {
PhoneNumberType numberType = phoneUtil.getNumberType(number);
if (numberType == PhoneNumberType.UNKNOWN) {
return "";
} else if (!canBeGeocoded(numberType)) {
return getCountryNameForNumber(number, languageCode);
}
return getDescriptionForValidNumber(number, languageCode);
}
/**
* As per {@link #getDescriptionForValidNumber(PhoneNumber, Locale, String)} but
* explicitly checks the validity of the number passed in.
*
* @param number the phone number for which we want to get a text description
* @param languageCode the language code for which the description should be written
* @param userRegion the region code for a given user. This region will be omitted from the
* description if the phone number comes from this region. It is a two-letter uppercase ISO
* country code as defined by ISO 3166-1.
* @return a text description for the given language code for the given phone number, or empty
* string if the number passed in is invalid
*/
public String getDescriptionForNumber(PhoneNumber number, Locale languageCode,
String userRegion) {
PhoneNumberType numberType = phoneUtil.getNumberType(number);
if (numberType == PhoneNumberType.UNKNOWN) {
return "";
} else if (!canBeGeocoded(numberType)) {
return getCountryNameForNumber(number, languageCode);
}
return getDescriptionForValidNumber(number, languageCode, userRegion);
}
/**
* A similar method is implemented as PhoneNumberUtil.isNumberGeographical, which performs a
* stricter check, as it determines if a number has a geographical association. Also, if new
* phone number types were added, we should check if this other method should be updated too.
*/
private boolean canBeGeocoded(PhoneNumberType numberType) {
return (numberType == PhoneNumberType.FIXED_LINE ||
numberType == PhoneNumberType.MOBILE ||
numberType == PhoneNumberType.FIXED_LINE_OR_MOBILE);
}
}