1 /*
2  * Copyright (C) 2013 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.android.contacts.common.util;
17 
18 import android.content.Context;
19 import android.telephony.PhoneNumberUtils;
20 import android.text.TextUtils;
21 import android.util.Log;
22 
23 import com.google.i18n.phonenumbers.NumberParseException;
24 import com.google.i18n.phonenumbers.PhoneNumberUtil;
25 import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
26 import com.google.i18n.phonenumbers.ShortNumberInfo;
27 
28 import java.util.Locale;
29 
30 /**
31  * This class wraps several PhoneNumberUtil calls and TelephonyManager calls. Some of them are
32  * the same as the ones in the framework's code base. We can remove those once they are part of
33  * the public API.
34  */
35 public class PhoneNumberHelper {
36 
37     private static final String LOG_TAG = PhoneNumberHelper.class.getSimpleName();
38 
39     /**
40      * Determines if the specified number is actually a URI (i.e. a SIP address) rather than a
41      * regular PSTN phone number, based on whether or not the number contains an "@" character.
42      *
43      * @param number Phone number
44      * @return true if number contains @
45      *
46      * TODO: Remove if PhoneNumberUtils.isUriNumber(String number) is made public.
47      */
isUriNumber(String number)48     public static boolean isUriNumber(String number) {
49         // Note we allow either "@" or "%40" to indicate a URI, in case
50         // the passed-in string is URI-escaped.  (Neither "@" nor "%40"
51         // will ever be found in a legal PSTN number.)
52         return number != null && (number.contains("@") || number.contains("%40"));
53     }
54 
55     /**
56      * Formats the phone number only if the given number hasn't been formatted.
57      * <p>
58      * The number which has only dailable character is treated as not being
59      * formatted.
60      *
61      * @param phoneNumber the number to be formatted.
62      * @param phoneNumberE164 The E164 format number whose country code is used if the given
63      * phoneNumber doesn't have the country code.
64      * @param defaultCountryIso The ISO 3166-1 two letters country code whose convention will
65      * be used if the phoneNumberE164 is null or invalid, or if phoneNumber contains IDD.
66      * @return The formatted number if the given number has been formatted, otherwise, return the
67      * given number.
68      *
69      * TODO: Remove if PhoneNumberUtils.formatNumber(String phoneNumber, String phoneNumberE164,
70      * String defaultCountryIso) is made public.
71      */
formatNumber( String phoneNumber, String phoneNumberE164, String defaultCountryIso)72     public static String formatNumber(
73             String phoneNumber, String phoneNumberE164, String defaultCountryIso) {
74         int len = phoneNumber.length();
75         for (int i = 0; i < len; i++) {
76             if (!PhoneNumberUtils.isDialable(phoneNumber.charAt(i))) {
77                 return phoneNumber;
78             }
79         }
80         PhoneNumberUtil util = PhoneNumberUtil.getInstance();
81         // Get the country code from phoneNumberE164
82         if (phoneNumberE164 != null && phoneNumberE164.length() >= 2
83                 && phoneNumberE164.charAt(0) == '+') {
84             try {
85                 // The number to be parsed is in E164 format, so the default region used doesn't
86                 // matter.
87                 PhoneNumber pn = util.parse(phoneNumberE164, "ZZ");
88                 String regionCode = util.getRegionCodeForNumber(pn);
89                 if (!TextUtils.isEmpty(regionCode) &&
90                         // This makes sure phoneNumber doesn't contain an IDD
91                         normalizeNumber(phoneNumber).indexOf(phoneNumberE164.substring(1)) <= 0) {
92                     defaultCountryIso = regionCode;
93                 }
94             } catch (NumberParseException e) {
95                 Log.w(LOG_TAG, "The number could not be parsed in E164 format!");
96             }
97         }
98 
99         String result = formatNumber(phoneNumber, defaultCountryIso);
100         return result == null ? phoneNumber : result;
101     }
102 
103     /**
104      * Format a phone number.
105      * <p>
106      * If the given number doesn't have the country code, the phone will be
107      * formatted to the default country's convention.
108      *
109      * @param phoneNumber The number to be formatted.
110      * @param defaultCountryIso The ISO 3166-1 two letters country code whose convention will
111      * be used if the given number doesn't have the country code.
112      * @return The formatted number, or null if the given number is not valid.
113      *
114      * TODO: Remove if PhoneNumberUtils.formatNumber(String phoneNumber, String defaultCountryIso)
115      * is made public.
116      */
formatNumber(String phoneNumber, String defaultCountryIso)117     public static String formatNumber(String phoneNumber, String defaultCountryIso) {
118         // Do not attempt to format numbers that start with a hash or star symbol.
119         if (phoneNumber.startsWith("#") || phoneNumber.startsWith("*")) {
120             return phoneNumber;
121         }
122 
123         final PhoneNumberUtil util = PhoneNumberUtil.getInstance();
124         String result = null;
125         try {
126             PhoneNumber pn = util.parseAndKeepRawInput(phoneNumber, defaultCountryIso);
127             result = util.formatInOriginalFormat(pn, defaultCountryIso);
128         } catch (NumberParseException e) {
129             Log.w(LOG_TAG, "Number could not be parsed with the given country code!");
130         }
131         return result;
132     }
133 
134     /**
135      * Normalize a phone number by removing the characters other than digits. If
136      * the given number has keypad letters, the letters will be converted to
137      * digits first.
138      *
139      * @param phoneNumber The number to be normalized.
140      * @return The normalized number.
141      *
142      * TODO: Remove if PhoneNumberUtils.normalizeNumber(String phoneNumber) is made public.
143      */
normalizeNumber(String phoneNumber)144     public static String normalizeNumber(String phoneNumber) {
145         StringBuilder sb = new StringBuilder();
146         int len = phoneNumber.length();
147         for (int i = 0; i < len; i++) {
148             char c = phoneNumber.charAt(i);
149             // Character.digit() supports ASCII and Unicode digits (fullwidth, Arabic-Indic, etc.)
150             int digit = Character.digit(c, 10);
151             if (digit != -1) {
152                 sb.append(digit);
153             } else if (i == 0 && c == '+') {
154                 sb.append(c);
155             } else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
156                 return normalizeNumber(PhoneNumberUtils.convertKeypadLettersToDigits(phoneNumber));
157             }
158         }
159         return sb.toString();
160     }
161 
162     /**
163      * @return the "username" part of the specified SIP address, i.e. the part before the "@"
164      * character (or "%40").
165      *
166      * @param number SIP address of the form "username@domainname" (or the URI-escaped equivalent
167      * "username%40domainname")
168      *
169      * TODO: Remove if PhoneNumberUtils.getUsernameFromUriNumber(String number) is made public.
170      */
getUsernameFromUriNumber(String number)171     public static String getUsernameFromUriNumber(String number) {
172         // The delimiter between username and domain name can be
173         // either "@" or "%40" (the URI-escaped equivalent.)
174         int delimiterIndex = number.indexOf('@');
175         if (delimiterIndex < 0) {
176             delimiterIndex = number.indexOf("%40");
177         }
178         if (delimiterIndex < 0) {
179             Log.w(LOG_TAG,
180                     "getUsernameFromUriNumber: no delimiter found in SIP addr '" + number + "'");
181             return number;
182         }
183         return number.substring(0, delimiterIndex);
184     }
185 }
186