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.Locale;
28 * An offline geocoder which provides geographical information related to a phone number.
30 * @author Shaopeng Jia
32 public class PhoneNumberOfflineGeocoder {
33 private static PhoneNumberOfflineGeocoder instance = null;
34 private static final String MAPPING_DATA_DIRECTORY =
35 "/com/google/i18n/phonenumbers/geocoding/data/";
36 private PrefixFileReader prefixFileReader = null;
38 private final PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
41 PhoneNumberOfflineGeocoder(String phonePrefixDataDirectory) {
42 prefixFileReader = new PrefixFileReader(phonePrefixDataDirectory);
46 * Gets a {@link PhoneNumberOfflineGeocoder} instance to carry out international phone number
49 * <p> The {@link PhoneNumberOfflineGeocoder} is implemented as a singleton. Therefore, calling
50 * this method multiple times will only result in one instance being created.
52 * @return a {@link PhoneNumberOfflineGeocoder} instance
54 public static synchronized PhoneNumberOfflineGeocoder getInstance() {
55 if (instance == null) {
56 instance = new PhoneNumberOfflineGeocoder(MAPPING_DATA_DIRECTORY);
62 * Returns the customary display name in the given language for the given territory the phone
65 private String getCountryNameForNumber(PhoneNumber number, Locale language) {
66 String regionCode = phoneUtil.getRegionCodeForNumber(number);
67 return getRegionDisplayName(regionCode, language);
71 * Returns the customary display name in the given language for the given region.
73 private String getRegionDisplayName(String regionCode, Locale language) {
74 return (regionCode == null || regionCode.equals("ZZ") ||
75 regionCode.equals(PhoneNumberUtil.REGION_CODE_FOR_NON_GEO_ENTITY))
76 ? "" : new Locale("", regionCode).getDisplayCountry(language);
80 * Returns a text description for the given phone number, in the language provided. The
81 * description might consist of the name of the country where the phone number is from, or the
82 * name of the geographical area the phone number is from if more detailed information is
85 * <p>This method assumes the validity of the number passed in has already been checked, and that
86 * the number is suitable for geocoding. We consider fixed-line and mobile numbers possible
87 * candidates for geocoding.
89 * @param number a valid phone number for which we want to get a text description
90 * @param languageCode the language code for which the description should be written
91 * @return a text description for the given language code for the given phone number
93 public String getDescriptionForValidNumber(PhoneNumber number, Locale languageCode) {
94 String langStr = languageCode.getLanguage();
95 String scriptStr = ""; // No script is specified
96 String regionStr = languageCode.getCountry();
98 String areaDescription;
99 String mobileToken = PhoneNumberUtil.getCountryMobileToken(number.getCountryCode());
100 String nationalNumber = phoneUtil.getNationalSignificantNumber(number);
101 if (!mobileToken.equals("") && nationalNumber.startsWith(mobileToken)) {
102 // In some countries, eg. Argentina, mobile numbers have a mobile token before the national
103 // destination code, this should be removed before geocoding.
104 nationalNumber = nationalNumber.substring(mobileToken.length());
105 String region = phoneUtil.getRegionCodeForCountryCode(number.getCountryCode());
106 PhoneNumber copiedNumber;
108 copiedNumber = phoneUtil.parse(nationalNumber, region);
109 } catch (NumberParseException e) {
110 // If this happens, just reuse what we had.
111 copiedNumber = number;
113 areaDescription = prefixFileReader.getDescriptionForNumber(copiedNumber, langStr, scriptStr,
116 areaDescription = prefixFileReader.getDescriptionForNumber(number, langStr, scriptStr,
119 return (areaDescription.length() > 0)
120 ? areaDescription : getCountryNameForNumber(number, languageCode);
124 * As per {@link #getDescriptionForValidNumber(PhoneNumber, Locale)} but also considers the
125 * region of the user. If the phone number is from the same region as the user, only a lower-level
126 * description will be returned, if one exists. Otherwise, the phone number's region will be
127 * returned, with optionally some more detailed information.
129 * <p>For example, for a user from the region "US" (United States), we would show "Mountain View,
130 * CA" for a particular number, omitting the United States from the description. For a user from
131 * the United Kingdom (region "GB"), for the same number we may show "Mountain View, CA, United
132 * States" or even just "United States".
134 * <p>This method assumes the validity of the number passed in has already been checked.
136 * @param number the phone number for which we want to get a text description
137 * @param languageCode the language code for which the description should be written
138 * @param userRegion the region code for a given user. This region will be omitted from the
139 * description if the phone number comes from this region. It is a two-letter uppercase ISO
140 * country code as defined by ISO 3166-1.
141 * @return a text description for the given language code for the given phone number, or empty
142 * string if the number passed in is invalid
144 public String getDescriptionForValidNumber(PhoneNumber number, Locale languageCode,
146 // If the user region matches the number's region, then we just show the lower-level
147 // description, if one exists - if no description exists, we will show the region(country) name
149 String regionCode = phoneUtil.getRegionCodeForNumber(number);
150 if (userRegion.equals(regionCode)) {
151 return getDescriptionForValidNumber(number, languageCode);
153 // Otherwise, we just show the region(country) name for now.
154 return getRegionDisplayName(regionCode, languageCode);
155 // TODO: Concatenate the lower-level and country-name information in an appropriate
156 // way for each language.
160 * As per {@link #getDescriptionForValidNumber(PhoneNumber, Locale)} but explicitly checks
161 * the validity of the number passed in.
163 * @param number the phone number for which we want to get a text description
164 * @param languageCode the language code for which the description should be written
165 * @return a text description for the given language code for the given phone number, or empty
166 * string if the number passed in is invalid
168 public String getDescriptionForNumber(PhoneNumber number, Locale languageCode) {
169 PhoneNumberType numberType = phoneUtil.getNumberType(number);
170 if (numberType == PhoneNumberType.UNKNOWN) {
172 } else if (!canBeGeocoded(numberType)) {
173 return getCountryNameForNumber(number, languageCode);
175 return getDescriptionForValidNumber(number, languageCode);
179 * As per {@link #getDescriptionForValidNumber(PhoneNumber, Locale, String)} but
180 * explicitly checks the validity of the number passed in.
182 * @param number the phone number for which we want to get a text description
183 * @param languageCode the language code for which the description should be written
184 * @param userRegion the region code for a given user. This region will be omitted from the
185 * description if the phone number comes from this region. It is a two-letter uppercase ISO
186 * country code as defined by ISO 3166-1.
187 * @return a text description for the given language code for the given phone number, or empty
188 * string if the number passed in is invalid
190 public String getDescriptionForNumber(PhoneNumber number, Locale languageCode,
192 PhoneNumberType numberType = phoneUtil.getNumberType(number);
193 if (numberType == PhoneNumberType.UNKNOWN) {
195 } else if (!canBeGeocoded(numberType)) {
196 return getCountryNameForNumber(number, languageCode);
198 return getDescriptionForValidNumber(number, languageCode, userRegion);
202 * A similar method is implemented as PhoneNumberUtil.isNumberGeographical, which performs a
203 * stricter check, as it determines if a number has a geographical association. Also, if new
204 * phone number types were added, we should check if this other method should be updated too.
206 private boolean canBeGeocoded(PhoneNumberType numberType) {
207 return (numberType == PhoneNumberType.FIXED_LINE ||
208 numberType == PhoneNumberType.MOBILE ||
209 numberType == PhoneNumberType.FIXED_LINE_OR_MOBILE);