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