2 * Copyright (C) 2011 The Libphonenumber Authors
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.google.i18n.phonenumbers.geocoding;
19 import com.google.i18n.phonenumbers.PhoneNumberUtil;
20 import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberType;
21 import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
22 import com.google.i18n.phonenumbers.prefixmapper.PrefixFileReader;
24 import java.util.Locale;
27 * An offline geocoder which provides geographical information related to a phone number.
29 * @author Shaopeng Jia
31 public class PhoneNumberOfflineGeocoder {
32 private static PhoneNumberOfflineGeocoder instance = null;
33 private static final String MAPPING_DATA_DIRECTORY =
34 "/com/google/i18n/phonenumbers/geocoding/data/";
35 private PrefixFileReader prefixFileReader = null;
37 private final PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
40 PhoneNumberOfflineGeocoder(String phonePrefixDataDirectory) {
41 prefixFileReader = new PrefixFileReader(phonePrefixDataDirectory);
45 * Gets a {@link PhoneNumberOfflineGeocoder} instance to carry out international phone number
48 * <p> The {@link PhoneNumberOfflineGeocoder} is implemented as a singleton. Therefore, calling
49 * this method multiple times will only result in one instance being created.
51 * @return a {@link PhoneNumberOfflineGeocoder} instance
53 public static synchronized PhoneNumberOfflineGeocoder getInstance() {
54 if (instance == null) {
55 instance = new PhoneNumberOfflineGeocoder(MAPPING_DATA_DIRECTORY);
61 * Returns the customary display name in the given language for the given territory the phone
64 private String getCountryNameForNumber(PhoneNumber number, Locale language) {
65 String regionCode = phoneUtil.getRegionCodeForNumber(number);
66 return getRegionDisplayName(regionCode, language);
70 * Returns the customary display name in the given language for the given region.
72 private String getRegionDisplayName(String regionCode, Locale language) {
73 return (regionCode == null || regionCode.equals("ZZ") ||
74 regionCode.equals(PhoneNumberUtil.REGION_CODE_FOR_NON_GEO_ENTITY))
75 ? "" : new Locale("", regionCode).getDisplayCountry(language);
79 * Returns a text description for the given phone number, in the language provided. The
80 * description might consist of the name of the country where the phone number is from, or the
81 * name of the geographical area the phone number is from if more detailed information is
84 * <p>This method assumes the validity of the number passed in has already been checked, and that
85 * the number is suitable for geocoding. We consider fixed-line and mobile numbers possible
86 * candidates for geocoding.
88 * @param number a valid phone number for which we want to get a text description
89 * @param languageCode the language code for which the description should be written
90 * @return a text description for the given language code for the given phone number
92 public String getDescriptionForValidNumber(PhoneNumber number, Locale languageCode) {
93 String langStr = languageCode.getLanguage();
94 String scriptStr = ""; // No script is specified
95 String regionStr = languageCode.getCountry();
97 String areaDescription;
98 String mobileToken = PhoneNumberUtil.getCountryMobileToken(number.getCountryCode());
99 String nationalNumber = phoneUtil.getNationalSignificantNumber(number);
100 if (!mobileToken.equals("") && nationalNumber.startsWith(mobileToken)) {
101 // In some countries, eg. Argentina, mobile numbers have a mobile token before the national
102 // destination code, this should be removed before geocoding.
103 nationalNumber = nationalNumber.substring(mobileToken.length());
104 PhoneNumber copiedNumber = new PhoneNumber();
105 copiedNumber.setCountryCode(number.getCountryCode());
106 copiedNumber.setNationalNumber(Long.parseLong(nationalNumber));
107 if (nationalNumber.startsWith("0")) {
108 copiedNumber.setItalianLeadingZero(true);
110 areaDescription = prefixFileReader.getDescriptionForNumber(copiedNumber, langStr, scriptStr,
113 areaDescription = prefixFileReader.getDescriptionForNumber(number, langStr, scriptStr,
116 return (areaDescription.length() > 0)
117 ? areaDescription : getCountryNameForNumber(number, languageCode);
121 * As per {@link #getDescriptionForValidNumber(PhoneNumber, Locale)} but also considers the
122 * region of the user. If the phone number is from the same region as the user, only a lower-level
123 * description will be returned, if one exists. Otherwise, the phone number's region will be
124 * returned, with optionally some more detailed information.
126 * <p>For example, for a user from the region "US" (United States), we would show "Mountain View,
127 * CA" for a particular number, omitting the United States from the description. For a user from
128 * the United Kingdom (region "GB"), for the same number we may show "Mountain View, CA, United
129 * States" or even just "United States".
131 * <p>This method assumes the validity of the number passed in has already been checked.
133 * @param number the phone number for which we want to get a text description
134 * @param languageCode the language code for which the description should be written
135 * @param userRegion the region code for a given user. This region will be omitted from the
136 * description if the phone number comes from this region. It is a two-letter uppercase ISO
137 * country code as defined by ISO 3166-1.
138 * @return a text description for the given language code for the given phone number, or empty
139 * string if the number passed in is invalid
141 public String getDescriptionForValidNumber(PhoneNumber number, Locale languageCode,
143 // If the user region matches the number's region, then we just show the lower-level
144 // description, if one exists - if no description exists, we will show the region(country) name
146 String regionCode = phoneUtil.getRegionCodeForNumber(number);
147 if (userRegion.equals(regionCode)) {
148 return getDescriptionForValidNumber(number, languageCode);
150 // Otherwise, we just show the region(country) name for now.
151 return getRegionDisplayName(regionCode, languageCode);
152 // TODO: Concatenate the lower-level and country-name information in an appropriate
153 // way for each language.
157 * As per {@link #getDescriptionForValidNumber(PhoneNumber, Locale)} but explicitly checks
158 * the validity of the number passed in.
160 * @param number the phone number for which we want to get a text description
161 * @param languageCode the language code for which the description should be written
162 * @return a text description for the given language code for the given phone number, or empty
163 * string if the number passed in is invalid
165 public String getDescriptionForNumber(PhoneNumber number, Locale languageCode) {
166 PhoneNumberType numberType = phoneUtil.getNumberType(number);
167 if (numberType == PhoneNumberType.UNKNOWN) {
169 } else if (!canBeGeocoded(numberType)) {
170 return getCountryNameForNumber(number, languageCode);
172 return getDescriptionForValidNumber(number, languageCode);
176 * As per {@link #getDescriptionForValidNumber(PhoneNumber, Locale, String)} but
177 * explicitly checks the validity of the number passed in.
179 * @param number the phone number for which we want to get a text description
180 * @param languageCode the language code for which the description should be written
181 * @param userRegion the region code for a given user. This region will be omitted from the
182 * description if the phone number comes from this region. It is a two-letter uppercase ISO
183 * country code as defined by ISO 3166-1.
184 * @return a text description for the given language code for the given phone number, or empty
185 * string if the number passed in is invalid
187 public String getDescriptionForNumber(PhoneNumber number, Locale languageCode,
189 PhoneNumberType numberType = phoneUtil.getNumberType(number);
190 if (numberType == PhoneNumberType.UNKNOWN) {
192 } else if (!canBeGeocoded(numberType)) {
193 return getCountryNameForNumber(number, languageCode);
195 return getDescriptionForValidNumber(number, languageCode, userRegion);
199 * A similar method is implemented as PhoneNumberUtil.isNumberGeographical, which performs a
200 * stricter check, as it determines if a number has a geographical association. Also, if new
201 * phone number types were added, we should check if this other method should be updated too.
203 private boolean canBeGeocoded(PhoneNumberType numberType) {
204 return (numberType == PhoneNumberType.FIXED_LINE ||
205 numberType == PhoneNumberType.MOBILE ||
206 numberType == PhoneNumberType.FIXED_LINE_OR_MOBILE);