summaryrefslogtreecommitdiffstats
path: root/src/com/android/contacts/common/util/PhoneNumberHelper.java
blob: 3c05705216b22b30ec508be52e2d66bd0eb7f3de (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
225
226
227
228
229
230
231
232
/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * 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.contacts.common.util;

import android.content.Context;
import android.location.Geocoder;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
import android.util.Log;

import com.android.contacts.common.GeoUtil;
import com.google.i18n.phonenumbers.NumberParseException;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
import com.google.i18n.phonenumbers.ShortNumberInfo;

import java.util.Locale;

/**
 * This class wraps several PhoneNumberUtil calls and TelephonyManager calls. Some of them are
 * the same as the ones in the framework's code base. We can remove those once they are part of
 * the public API.
 */
public class PhoneNumberHelper {

    private static final String LOG_TAG = PhoneNumberHelper.class.getSimpleName();

    private static final String KOREA_ISO_COUNTRY_CODE = "KR";
    /**
     * Determines if the specified number is actually a URI (i.e. a SIP address) rather than a
     * regular PSTN phone number, based on whether or not the number contains an "@" character.
     *
     * @param number Phone number
     * @return true if number contains @
     *
     * TODO: Remove if PhoneNumberUtils.isUriNumber(String number) is made public.
     */
    public static boolean isUriNumber(String number) {
        // Note we allow either "@" or "%40" to indicate a URI, in case
        // the passed-in string is URI-escaped.  (Neither "@" nor "%40"
        // will ever be found in a legal PSTN number.)
        return number != null && (number.contains("@") || number.contains("%40"));
    }

    /**
     * Formats the phone number only if the given number hasn't been formatted.
     * <p>
     * The number which has only dailable character is treated as not being
     * formatted.
     *
     * @param phoneNumber the number to be formatted.
     * @param phoneNumberE164 The E164 format number whose country code is used if the given
     * phoneNumber doesn't have the country code.
     * @param defaultCountryIso The ISO 3166-1 two letters country code whose convention will
     * be used if the phoneNumberE164 is null or invalid, or if phoneNumber contains IDD.
     * @return The formatted number if the given number has been formatted, otherwise, return the
     * given number.
     *
     * TODO: Remove if PhoneNumberUtils.formatNumber(String phoneNumber, String phoneNumberE164,
     * String defaultCountryIso) is made public.
     */
    public static String formatNumber(
            String phoneNumber, String phoneNumberE164, String defaultCountryIso) {
        int len = phoneNumber.length();
        for (int i = 0; i < len; i++) {
            if (!PhoneNumberUtils.isDialable(phoneNumber.charAt(i))) {
                return phoneNumber;
            }
        }
        PhoneNumberUtil util = PhoneNumberUtil.getInstance();
        // Get the country code from phoneNumberE164
        if (phoneNumberE164 != null && phoneNumberE164.length() >= 2
                && phoneNumberE164.charAt(0) == '+') {
            try {
                // The number to be parsed is in E164 format, so the default region used doesn't
                // matter.
                PhoneNumber pn = util.parse(phoneNumberE164, "ZZ");
                String regionCode = util.getRegionCodeForNumber(pn);
                if (!TextUtils.isEmpty(regionCode) &&
                        // This makes sure phoneNumber doesn't contain an IDD
                        normalizeNumber(phoneNumber).indexOf(phoneNumberE164.substring(1)) <= 0) {
                    defaultCountryIso = regionCode;
                }
            } catch (NumberParseException e) {
                Log.w(LOG_TAG, "The number could not be parsed in E164 format!");
            }
        }

        String result = formatNumber(phoneNumber, defaultCountryIso);
        return result == null ? phoneNumber : result;
    }

    /**
     * Format a phone number.
     * <p>
     * If the given number doesn't have the country code, the phone will be
     * formatted to the default country's convention.
     *
     * @param phoneNumber The number to be formatted.
     * @param defaultCountryIso The ISO 3166-1 two letters country code whose convention will
     * be used if the given number doesn't have the country code.
     * @return The formatted number, or null if the given number is not valid.
     *
     * TODO: Remove if PhoneNumberUtils.formatNumber(String phoneNumber, String defaultCountryIso)
     * is made public.
     */
    public static String formatNumber(String phoneNumber, String defaultCountryIso) {
        // Do not attempt to format numbers that are empty or start with a hash
        // or star symbol.
        if (TextUtils.isEmpty(phoneNumber) || phoneNumber.startsWith("#") ||
                phoneNumber.startsWith("*")) {
            return phoneNumber;
        }

        final PhoneNumberUtil util = PhoneNumberUtil.getInstance();
        String result = null;
        try {
            PhoneNumber pn = util.parseAndKeepRawInput(phoneNumber, defaultCountryIso);
            /**
             * Need to reformat any local Korean phone numbers (when the user is in Korea) with
             * country code to corresponding national format which would replace the leading
             * +82 with 0.
             */
            if (KOREA_ISO_COUNTRY_CODE.equals(defaultCountryIso) &&
                    (pn.getCountryCode() == util.getCountryCodeForRegion(KOREA_ISO_COUNTRY_CODE)) &&
                    (pn.getCountryCodeSource() ==
                            PhoneNumber.CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN)) {
                result = util.format(pn, PhoneNumberUtil.PhoneNumberFormat.NATIONAL);
            } else {
                result = util.formatInOriginalFormat(pn, defaultCountryIso);
            }
        } catch (NumberParseException e) {
            Log.w(LOG_TAG, "Number could not be parsed with the given country code!");
        }
        return result;
    }

    public static String formatPhoneNumber(Context context, String phoneNumber) {
        return formatNumber(phoneNumber, GeoUtil.getCurrentCountryIso(context));
    }

    /**
     * Normalize a phone number by removing the characters other than digits. If
     * the given number has keypad letters, the letters will be converted to
     * digits first.
     *
     * @param phoneNumber The number to be normalized.
     * @return The normalized number.
     *
     * TODO: Remove if PhoneNumberUtils.normalizeNumber(String phoneNumber) is made public.
     */
    public static String normalizeNumber(String phoneNumber) {
        StringBuilder sb = new StringBuilder();
        int len = phoneNumber.length();
        for (int i = 0; i < len; i++) {
            char c = phoneNumber.charAt(i);
            // Character.digit() supports ASCII and Unicode digits (fullwidth, Arabic-Indic, etc.)
            int digit = Character.digit(c, 10);
            if (digit != -1) {
                sb.append(digit);
            } else if (i == 0 && c == '+') {
                sb.append(c);
            } else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
                return normalizeNumber(PhoneNumberUtils.convertKeypadLettersToDigits(phoneNumber));
            }
        }
        return sb.toString();
    }

    /**
     * @return the "username" part of the specified SIP address, i.e. the part before the "@"
     * character (or "%40").
     *
     * @param number SIP address of the form "username@domainname" (or the URI-escaped equivalent
     * "username%40domainname")
     *
     * TODO: Remove if PhoneNumberUtils.getUsernameFromUriNumber(String number) is made public.
     */
    public static String getUsernameFromUriNumber(String number) {
        // The delimiter between username and domain name can be
        // either "@" or "%40" (the URI-escaped equivalent.)
        int delimiterIndex = number.indexOf('@');
        if (delimiterIndex < 0) {
            delimiterIndex = number.indexOf("%40");
        }
        if (delimiterIndex < 0) {
            Log.w(LOG_TAG,
                    "getUsernameFromUriNumber: no delimiter found in SIP addr '" + number + "'");
            return number;
        }
        return number.substring(0, delimiterIndex);
    }

    /**
     * Determine whether a phone number matches a valid phone number pattern for a specified region.
     *
     * @param context application context
     * @param phoneNumber The number to be formatted.
     * @param defaultCountryIso The ISO 3166-1 two letters country code whose convention will
     * be used if the given number doesn't have the country code.
     * @return boolean representing whether valid phone number or not.
     */
    public static boolean isValidNumber(Context context, String phoneNumber,
            String defaultCountryIso) {
        final String iso = TextUtils.isEmpty(defaultCountryIso) ?
                GeoUtil.getCurrentCountryIso(context) : defaultCountryIso;
        final PhoneNumberUtil util = PhoneNumberUtil.getInstance();
        boolean result = false;

        try {
            PhoneNumber pn = util.parseAndKeepRawInput(phoneNumber, iso);
            result = util.isValidNumber(pn);
        } catch (NumberParseException e) {
            Log.w(LOG_TAG, "Number could not be parsed with the given country code!", e);
        }

        return result;
    }
}