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 private static final String KOREA_ISO_COUNTRY_CODE = "KR"; 40 /** 41 * Determines if the specified number is actually a URI (i.e. a SIP address) rather than a 42 * regular PSTN phone number, based on whether or not the number contains an "@" character. 43 * 44 * @param number Phone number 45 * @return true if number contains @ 46 * 47 * TODO: Remove if PhoneNumberUtils.isUriNumber(String number) is made public. 48 */ isUriNumber(String number)49 public static boolean isUriNumber(String number) { 50 // Note we allow either "@" or "%40" to indicate a URI, in case 51 // the passed-in string is URI-escaped. (Neither "@" nor "%40" 52 // will ever be found in a legal PSTN number.) 53 return number != null && (number.contains("@") || number.contains("%40")); 54 } 55 56 /** 57 * Formats the phone number only if the given number hasn't been formatted. 58 * <p> 59 * The number which has only dailable character is treated as not being 60 * formatted. 61 * 62 * @param phoneNumber the number to be formatted. 63 * @param phoneNumberE164 The E164 format number whose country code is used if the given 64 * phoneNumber doesn't have the country code. 65 * @param defaultCountryIso The ISO 3166-1 two letters country code whose convention will 66 * be used if the phoneNumberE164 is null or invalid, or if phoneNumber contains IDD. 67 * @return The formatted number if the given number has been formatted, otherwise, return the 68 * given number. 69 * 70 * TODO: Remove if PhoneNumberUtils.formatNumber(String phoneNumber, String phoneNumberE164, 71 * String defaultCountryIso) is made public. 72 */ formatNumber( String phoneNumber, String phoneNumberE164, String defaultCountryIso)73 public static String formatNumber( 74 String phoneNumber, String phoneNumberE164, String defaultCountryIso) { 75 int len = phoneNumber.length(); 76 for (int i = 0; i < len; i++) { 77 if (!PhoneNumberUtils.isDialable(phoneNumber.charAt(i))) { 78 return phoneNumber; 79 } 80 } 81 PhoneNumberUtil util = PhoneNumberUtil.getInstance(); 82 // Get the country code from phoneNumberE164 83 if (phoneNumberE164 != null && phoneNumberE164.length() >= 2 84 && phoneNumberE164.charAt(0) == '+') { 85 try { 86 // The number to be parsed is in E164 format, so the default region used doesn't 87 // matter. 88 PhoneNumber pn = util.parse(phoneNumberE164, "ZZ"); 89 String regionCode = util.getRegionCodeForNumber(pn); 90 if (!TextUtils.isEmpty(regionCode) && 91 // This makes sure phoneNumber doesn't contain an IDD 92 normalizeNumber(phoneNumber).indexOf(phoneNumberE164.substring(1)) <= 0) { 93 defaultCountryIso = regionCode; 94 } 95 } catch (NumberParseException e) { 96 Log.w(LOG_TAG, "The number could not be parsed in E164 format!"); 97 } 98 } 99 100 String result = formatNumber(phoneNumber, defaultCountryIso); 101 return result == null ? phoneNumber : result; 102 } 103 104 /** 105 * Format a phone number. 106 * <p> 107 * If the given number doesn't have the country code, the phone will be 108 * formatted to the default country's convention. 109 * 110 * @param phoneNumber The number to be formatted. 111 * @param defaultCountryIso The ISO 3166-1 two letters country code whose convention will 112 * be used if the given number doesn't have the country code. 113 * @return The formatted number, or null if the given number is not valid. 114 * 115 * TODO: Remove if PhoneNumberUtils.formatNumber(String phoneNumber, String defaultCountryIso) 116 * is made public. 117 */ formatNumber(String phoneNumber, String defaultCountryIso)118 public static String formatNumber(String phoneNumber, String defaultCountryIso) { 119 // Do not attempt to format numbers that start with a hash or star symbol. 120 if (phoneNumber.startsWith("#") || phoneNumber.startsWith("*")) { 121 return phoneNumber; 122 } 123 124 final PhoneNumberUtil util = PhoneNumberUtil.getInstance(); 125 String result = null; 126 try { 127 PhoneNumber pn = util.parseAndKeepRawInput(phoneNumber, defaultCountryIso); 128 /** 129 * Need to reformat any local Korean phone numbers (when the user is in Korea) with 130 * country code to corresponding national format which would replace the leading 131 * +82 with 0. 132 */ 133 if (KOREA_ISO_COUNTRY_CODE.equals(defaultCountryIso) && 134 (pn.getCountryCode() == util.getCountryCodeForRegion(KOREA_ISO_COUNTRY_CODE)) && 135 (pn.getCountryCodeSource() == 136 PhoneNumber.CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN)) { 137 result = util.format(pn, PhoneNumberUtil.PhoneNumberFormat.NATIONAL); 138 } else { 139 result = util.formatInOriginalFormat(pn, defaultCountryIso); 140 } 141 } catch (NumberParseException e) { 142 Log.w(LOG_TAG, "Number could not be parsed with the given country code!"); 143 } 144 return result; 145 } 146 147 /** 148 * Normalize a phone number by removing the characters other than digits. If 149 * the given number has keypad letters, the letters will be converted to 150 * digits first. 151 * 152 * @param phoneNumber The number to be normalized. 153 * @return The normalized number. 154 * 155 * TODO: Remove if PhoneNumberUtils.normalizeNumber(String phoneNumber) is made public. 156 */ normalizeNumber(String phoneNumber)157 public static String normalizeNumber(String phoneNumber) { 158 StringBuilder sb = new StringBuilder(); 159 int len = phoneNumber.length(); 160 for (int i = 0; i < len; i++) { 161 char c = phoneNumber.charAt(i); 162 // Character.digit() supports ASCII and Unicode digits (fullwidth, Arabic-Indic, etc.) 163 int digit = Character.digit(c, 10); 164 if (digit != -1) { 165 sb.append(digit); 166 } else if (i == 0 && c == '+') { 167 sb.append(c); 168 } else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { 169 return normalizeNumber(PhoneNumberUtils.convertKeypadLettersToDigits(phoneNumber)); 170 } 171 } 172 return sb.toString(); 173 } 174 175 /** 176 * @return the "username" part of the specified SIP address, i.e. the part before the "@" 177 * character (or "%40"). 178 * 179 * @param number SIP address of the form "username@domainname" (or the URI-escaped equivalent 180 * "username%40domainname") 181 * 182 * TODO: Remove if PhoneNumberUtils.getUsernameFromUriNumber(String number) is made public. 183 */ getUsernameFromUriNumber(String number)184 public static String getUsernameFromUriNumber(String number) { 185 // The delimiter between username and domain name can be 186 // either "@" or "%40" (the URI-escaped equivalent.) 187 int delimiterIndex = number.indexOf('@'); 188 if (delimiterIndex < 0) { 189 delimiterIndex = number.indexOf("%40"); 190 } 191 if (delimiterIndex < 0) { 192 Log.w(LOG_TAG, 193 "getUsernameFromUriNumber: no delimiter found in SIP addr '" + number + "'"); 194 return number; 195 } 196 return number.substring(0, delimiterIndex); 197 } 198 } 199