summaryrefslogtreecommitdiff
path: root/repackaged/geocoder/src/com/android/i18n/phonenumbers/geocoding/PhoneNumberOfflineGeocoder.java
blob: 34829607a1f92b9740edeb22deb78cdffe79e832 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
/* GENERATED SOURCE. DO NOT MODIFY. */
/*
 * 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.android.i18n.phonenumbers.geocoding;

import com.android.i18n.phonenumbers.NumberParseException;
import com.android.i18n.phonenumbers.PhoneNumberUtil;
import com.android.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberType;
import com.android.i18n.phonenumbers.Phonenumber.PhoneNumber;
import com.android.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
 * @hide This class is not part of the Android public SDK API
 */
public class PhoneNumberOfflineGeocoder {
  private static PhoneNumberOfflineGeocoder instance = null;
  private static final String MAPPING_DATA_DIRECTORY =
      "/com/android/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.
   *
   * <p> 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
   */
  @android.compat.annotation.UnsupportedAppUsage
  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<String> regionCodes =
        phoneUtil.getRegionCodesForCountryCode(number.getCountryCode());
    if (regionCodes.size() == 1) {
      return getRegionDisplayName(regionCodes.get(0), language);
    } else {
      String regionWhereNumberIsValid = "ZZ";
      for (String regionCode : regionCodes) {
        if (phoneUtil.isValidNumberForRegion(number, regionCode)) {
          // If the number has already been found valid for one region, then we don't know which
          // region it belongs to so we return nothing.
          if (!regionWhereNumberIsValid.equals("ZZ")) {
            return "";
          }
          regionWhereNumberIsValid = regionCode;
        }
      }
      return getRegionDisplayName(regionWhereNumberIsValid, language);
    }
  }

  /**
   * Returns the customary display name in the given language for the given region.
   */
  private String getRegionDisplayName(String regionCode, Locale language) {
    return (regionCode == null || regionCode.equals("ZZ")
        || regionCode.equals(PhoneNumberUtil.REGION_CODE_FOR_NON_GEO_ENTITY))
        ? "" : new Locale("", regionCode).getDisplayCountry(language);
  }

  /**
   * Returns a text description for the given phone number, in the language provided. The
   * description might consist of the name of the country where the phone number is from, or the
   * name of the geographical area the phone number is from if more detailed information is
   * available.
   *
   * <p>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, or an
   *     empty string if the number could come from multiple countries, or the country code is
   *     in fact invalid
   */
  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.
   *
   * <p>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".
   *
   * <p>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 should be a two-letter
   *     upper-case CLDR region code.
   * @return  a text description for the given language code for the given phone number, or an
   *     empty string if the number could come from multiple countries, or the country code is
   *     in fact 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 or could belong to multiple countries
   */
  @android.compat.annotation.UnsupportedAppUsage
  public String getDescriptionForNumber(PhoneNumber number, Locale languageCode) {
    PhoneNumberType numberType = phoneUtil.getNumberType(number);
    if (numberType == PhoneNumberType.UNKNOWN) {
      return "";
    } else if (!phoneUtil.isNumberGeographical(numberType, number.getCountryCode())) {
      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 should be a two-letter
   *     upper-case CLDR region code.
   * @return  a text description for the given language code for the given phone number, or empty
   *     string if the number passed in is invalid or could belong to multiple countries
   */
  public String getDescriptionForNumber(PhoneNumber number, Locale languageCode,
                                        String userRegion) {
    PhoneNumberType numberType = phoneUtil.getNumberType(number);
    if (numberType == PhoneNumberType.UNKNOWN) {
      return "";
    } else if (!phoneUtil.isNumberGeographical(numberType, number.getCountryCode())) {
      return getCountryNameForNumber(number, languageCode);
    }
    return getDescriptionForValidNumber(number, languageCode, userRegion);
  }
}