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.NumberParseException;
20 import com.google.i18n.phonenumbers.PhoneNumberUtil;
21 import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberType;
22 import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
23 import com.google.i18n.phonenumbers.prefixmapper.PrefixFileReader;
25 import java.util.List;
26 import java.util.Locale;
29 * An offline geocoder which provides geographical information related to a phone number.
31 * @author Shaopeng Jia
33 public class PhoneNumberOfflineGeocoder {
34 private static PhoneNumberOfflineGeocoder instance = null;
35 private static final String MAPPING_DATA_DIRECTORY =
36 "/com/google/i18n/phonenumbers/geocoding/data/";
37 private PrefixFileReader prefixFileReader = null;
39 private final PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
42 PhoneNumberOfflineGeocoder(String phonePrefixDataDirectory) {
43 prefixFileReader = new PrefixFileReader(phonePrefixDataDirectory);
47 * Gets a {@link PhoneNumberOfflineGeocoder} instance to carry out international phone number
50 * <p> The {@link PhoneNumberOfflineGeocoder} is implemented as a singleton. Therefore, calling
51 * this method multiple times will only result in one instance being created.
53 * @return a {@link PhoneNumberOfflineGeocoder} instance
55 public static synchronized PhoneNumberOfflineGeocoder getInstance() {
56 if (instance == null) {
57 instance = new PhoneNumberOfflineGeocoder(MAPPING_DATA_DIRECTORY);
63 * Returns the customary display name in the given language for the given territory the phone
64 * number is from. If it could be from many territories, nothing is returned.
66 private String getCountryNameForNumber(PhoneNumber number, Locale language) {
67 List<String> regionCodes =
68 phoneUtil.getRegionCodesForCountryCode(number.getCountryCode());
69 if (regionCodes.size() == 1) {
70 return getRegionDisplayName(regionCodes.get(0), language);
72 String regionWhereNumberIsValid = "ZZ";
73 for (String regionCode : regionCodes) {
74 if (phoneUtil.isValidNumberForRegion(number, regionCode)) {
75 if (!regionWhereNumberIsValid.equals("ZZ")) {
76 // If we can't assign the phone number as definitely belonging to only one territory,
77 // then we return nothing.
80 regionWhereNumberIsValid = regionCode;
83 return getRegionDisplayName(regionWhereNumberIsValid, language);
88 * Returns the customary display name in the given language for the given region.
90 private String getRegionDisplayName(String regionCode, Locale language) {
91 return (regionCode == null || regionCode.equals("ZZ") ||
92 regionCode.equals(PhoneNumberUtil.REGION_CODE_FOR_NON_GEO_ENTITY))
93 ? "" : new Locale("", regionCode).getDisplayCountry(language);
97 * Returns a text description for the given phone number, in the language provided. The
98 * description might consist of the name of the country where the phone number is from, or the
99 * name of the geographical area the phone number is from if more detailed information is
102 * <p>This method assumes the validity of the number passed in has already been checked, and that
103 * the number is suitable for geocoding. We consider fixed-line and mobile numbers possible
104 * candidates for geocoding.
106 * @param number a valid phone number for which we want to get a text description
107 * @param languageCode the language code for which the description should be written
108 * @return a text description for the given language code for the given phone number
110 public String getDescriptionForValidNumber(PhoneNumber number, Locale languageCode) {
111 String langStr = languageCode.getLanguage();
112 String scriptStr = ""; // No script is specified
113 String regionStr = languageCode.getCountry();
115 String areaDescription;
116 String mobileToken = PhoneNumberUtil.getCountryMobileToken(number.getCountryCode());
117 String nationalNumber = phoneUtil.getNationalSignificantNumber(number);
118 if (!mobileToken.equals("") && nationalNumber.startsWith(mobileToken)) {
119 // In some countries, eg. Argentina, mobile numbers have a mobile token before the national
120 // destination code, this should be removed before geocoding.
121 nationalNumber = nationalNumber.substring(mobileToken.length());
122 String region = phoneUtil.getRegionCodeForCountryCode(number.getCountryCode());
123 PhoneNumber copiedNumber;
125 copiedNumber = phoneUtil.parse(nationalNumber, region);
126 } catch (NumberParseException e) {
127 // If this happens, just reuse what we had.
128 copiedNumber = number;
130 areaDescription = prefixFileReader.getDescriptionForNumber(copiedNumber, langStr, scriptStr,
133 areaDescription = prefixFileReader.getDescriptionForNumber(number, langStr, scriptStr,
136 return (areaDescription.length() > 0)
137 ? areaDescription : getCountryNameForNumber(number, languageCode);
141 * As per {@link #getDescriptionForValidNumber(PhoneNumber, Locale)} but also considers the
142 * region of the user. If the phone number is from the same region as the user, only a lower-level
143 * description will be returned, if one exists. Otherwise, the phone number's region will be
144 * returned, with optionally some more detailed information.
146 * <p>For example, for a user from the region "US" (United States), we would show "Mountain View,
147 * CA" for a particular number, omitting the United States from the description. For a user from
148 * the United Kingdom (region "GB"), for the same number we may show "Mountain View, CA, United
149 * States" or even just "United States".
151 * <p>This method assumes the validity of the number passed in has already been checked.
153 * @param number the phone number for which we want to get a text description
154 * @param languageCode the language code for which the description should be written
155 * @param userRegion the region code for a given user. This region will be omitted from the
156 * description if the phone number comes from this region. It is a two-letter uppercase ISO
157 * country code as defined by ISO 3166-1.
158 * @return a text description for the given language code for the given phone number, or empty
159 * string if the number passed in is invalid
161 public String getDescriptionForValidNumber(PhoneNumber number, Locale languageCode,
163 // If the user region matches the number's region, then we just show the lower-level
164 // description, if one exists - if no description exists, we will show the region(country) name
166 String regionCode = phoneUtil.getRegionCodeForNumber(number);
167 if (userRegion.equals(regionCode)) {
168 return getDescriptionForValidNumber(number, languageCode);
170 // Otherwise, we just show the region(country) name for now.
171 return getRegionDisplayName(regionCode, languageCode);
172 // TODO: Concatenate the lower-level and country-name information in an appropriate
173 // way for each language.
177 * As per {@link #getDescriptionForValidNumber(PhoneNumber, Locale)} but explicitly checks
178 * the validity of the number passed in.
180 * @param number the phone number for which we want to get a text description
181 * @param languageCode the language code for which the description should be written
182 * @return a text description for the given language code for the given phone number, or empty
183 * string if the number passed in is invalid
185 public String getDescriptionForNumber(PhoneNumber number, Locale languageCode) {
186 PhoneNumberType numberType = phoneUtil.getNumberType(number);
187 if (numberType == PhoneNumberType.UNKNOWN) {
189 } else if (!canBeGeocoded(numberType)) {
190 return getCountryNameForNumber(number, languageCode);
192 return getDescriptionForValidNumber(number, languageCode);
196 * As per {@link #getDescriptionForValidNumber(PhoneNumber, Locale, String)} but
197 * explicitly checks the validity of the number passed in.
199 * @param number the phone number for which we want to get a text description
200 * @param languageCode the language code for which the description should be written
201 * @param userRegion the region code for a given user. This region will be omitted from the
202 * description if the phone number comes from this region. It is a two-letter uppercase ISO
203 * country code as defined by ISO 3166-1.
204 * @return a text description for the given language code for the given phone number, or empty
205 * string if the number passed in is invalid
207 public String getDescriptionForNumber(PhoneNumber number, Locale languageCode,
209 PhoneNumberType numberType = phoneUtil.getNumberType(number);
210 if (numberType == PhoneNumberType.UNKNOWN) {
212 } else if (!canBeGeocoded(numberType)) {
213 return getCountryNameForNumber(number, languageCode);
215 return getDescriptionForValidNumber(number, languageCode, userRegion);
219 * A similar method is implemented as PhoneNumberUtil.isNumberGeographical, which performs a
220 * stricter check, as it determines if a number has a geographical association. Also, if new
221 * phone number types were added, we should check if this other method should be updated too.
223 private boolean canBeGeocoded(PhoneNumberType numberType) {
224 return (numberType == PhoneNumberType.FIXED_LINE ||
225 numberType == PhoneNumberType.MOBILE ||
226 numberType == PhoneNumberType.FIXED_LINE_OR_MOBILE);