/* * Copyright (C) 2006 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 android.telephony; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.SystemProperties; import android.provider.Contacts; import android.text.Editable; import android.text.SpannableStringBuilder; import android.text.TextUtils; import android.util.SparseIntArray; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Various utilities for dealing with phone number strings. */ public class PhoneNumberUtils { /* * Special characters * * (See "What is a phone number?" doc) * 'p' --- GSM pause character, same as comma * 'n' --- GSM wild character * 'w' --- GSM wait character */ public static final char PAUSE = ','; public static final char WAIT = ';'; public static final char WILD = 'N'; /* * TOA = TON + NPI * See TS 24.008 section 10.5.4.7 for details. * These are the only really useful TOA values */ public static final int TOA_International = 0x91; public static final int TOA_Unknown = 0x81; /* * global-phone-number = ["+"] 1*( DIGIT / written-sep ) * written-sep = ("-"/".") */ private static final Pattern GLOBAL_PHONE_NUMBER_PATTERN = Pattern.compile("[\\+]?[0-9.-]+"); /** True if c is ISO-LATIN characters 0-9 */ public static boolean isISODigit (char c) { return c >= '0' && c <= '9'; } /** True if c is ISO-LATIN characters 0-9, *, # */ public final static boolean is12Key(char c) { return (c >= '0' && c <= '9') || c == '*' || c == '#'; } /** True if c is ISO-LATIN characters 0-9, *, # , +, WILD */ public final static boolean isDialable(char c) { return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+' || c == WILD; } /** True if c is ISO-LATIN characters 0-9, *, # , + (no WILD) */ public final static boolean isReallyDialable(char c) { return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+'; } /** True if c is ISO-LATIN characters 0-9, *, # , +, WILD, WAIT, PAUSE */ public final static boolean isNonSeparator(char c) { return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+' || c == WILD || c == WAIT || c == PAUSE; } /** This any anything to the right of this char is part of the * post-dial string (eg this is PAUSE or WAIT) */ public final static boolean isStartsPostDial (char c) { return c == PAUSE || c == WAIT; } /** Extracts the phone number from an Intent. * * @param intent the intent to get the number of * @param context a context to use for database access * * @return the phone number that would be called by the intent, or * <code>null</code> if the number cannot be found. */ public static String getNumberFromIntent(Intent intent, Context context) { String number = null; Uri uri = intent.getData(); String scheme = uri.getScheme(); if (scheme.equals("tel")) { return uri.getSchemeSpecificPart(); } if (scheme.equals("voicemail")) { return TelephonyManager.getDefault().getVoiceMailNumber(); } if (context == null) { return null; } String type = intent.resolveType(context); Cursor c = context.getContentResolver().query( uri, new String[]{ Contacts.People.Phones.NUMBER }, null, null, null); if (c != null) { try { if (c.moveToFirst()) { number = c.getString( c.getColumnIndex(Contacts.People.Phones.NUMBER)); } } finally { c.close(); } } return number; } /** Extracts the network address portion and canonicalizes * (filters out separators.) * Network address portion is everything up to DTMF control digit * separators (pause or wait), but without non-dialable characters. * * Please note that the GSM wild character is allowed in the result. * This must be resolved before dialing. * * Allows + only in the first position in the result string. * * Returns null if phoneNumber == null */ public static String extractNetworkPortion(String phoneNumber) { if (phoneNumber == null) { return null; } int len = phoneNumber.length(); StringBuilder ret = new StringBuilder(len); boolean firstCharAdded = false; for (int i = 0; i < len; i++) { char c = phoneNumber.charAt(i); if (isDialable(c) && (c != '+' || !firstCharAdded)) { firstCharAdded = true; ret.append(c); } else if (isStartsPostDial (c)) { break; } } return ret.toString(); } /** * Strips separators from a phone number string. * @param phoneNumber phone number to strip. * @return phone string stripped of separators. */ public static String stripSeparators(String phoneNumber) { if (phoneNumber == null) { return null; } int len = phoneNumber.length(); StringBuilder ret = new StringBuilder(len); for (int i = 0; i < len; i++) { char c = phoneNumber.charAt(i); if (isNonSeparator(c)) { ret.append(c); } } return ret.toString(); } /** or -1 if both are negative */ static private int minPositive (int a, int b) { if (a >= 0 && b >= 0) { return (a < b) ? a : b; } else if (a >= 0) { /* && b < 0 */ return a; } else if (b >= 0) { /* && a < 0 */ return b; } else { /* a < 0 && b < 0 */ return -1; } } /** index of the last character of the network portion * (eg anything after is a post-dial string) */ static private int indexOfLastNetworkChar(String a) { int pIndex, wIndex; int origLength; int trimIndex; origLength = a.length(); pIndex = a.indexOf(PAUSE); wIndex = a.indexOf(WAIT); trimIndex = minPositive(pIndex, wIndex); if (trimIndex < 0) { return origLength - 1; } else { return trimIndex - 1; } } /** * Extracts the post-dial sequence of DTMF control digits, pauses, and * waits. Strips separators. This string may be empty, but will not be null * unless phoneNumber == null. * * Returns null if phoneNumber == null */ public static String extractPostDialPortion(String phoneNumber) { if (phoneNumber == null) return null; int trimIndex; StringBuilder ret = new StringBuilder(); trimIndex = indexOfLastNetworkChar (phoneNumber); for (int i = trimIndex + 1, s = phoneNumber.length() ; i < s; i++ ) { char c = phoneNumber.charAt(i); if (isNonSeparator(c)) { ret.append(c); } } return ret.toString(); } /** * Compare phone numbers a and b, return true if they're identical * enough for caller ID purposes. * * - Compares from right to left * - requires MIN_MATCH (5) characters to match * - handles common trunk prefixes and international prefixes * (basically, everything except the Russian trunk prefix) * * Tolerates nulls */ public static boolean compare(String a, String b) { int ia, ib; int matched; if (a == null || b == null) return a == b; if (a.length() == 0 || b.length() == 0) { return false; } ia = indexOfLastNetworkChar (a); ib = indexOfLastNetworkChar (b); matched = 0; while (ia >= 0 && ib >=0) { char ca, cb; boolean skipCmp = false; ca = a.charAt(ia); if (!isDialable(ca)) { ia--; skipCmp = true; } cb = b.charAt(ib); if (!isDialable(cb)) { ib--; skipCmp = true; } if (!skipCmp) { if (cb != ca && ca != WILD && cb != WILD) { break; } ia--; ib--; matched++; } } if (matched < MIN_MATCH) { int aLen = a.length(); // if the input strings match, but their lengths < MIN_MATCH, // treat them as equal. if (aLen == b.length() && aLen == matched) { return true; } return false; } // At least one string has matched completely; if (matched >= MIN_MATCH && (ia < 0 || ib < 0)) { return true; } /* * Now, what remains must be one of the following for a * match: * * - a '+' on one and a '00' or a '011' on the other * - a '0' on one and a (+,00)<country code> on the other * (for this, a '0' and a '00' prefix would have succeeded above) */ if (matchIntlPrefix(a, ia + 1) && matchIntlPrefix (b, ib +1) ) { return true; } if (matchTrunkPrefix(a, ia + 1) && matchIntlPrefixAndCC(b, ib +1) ) { return true; } if (matchTrunkPrefix(b, ib + 1) && matchIntlPrefixAndCC(a, ia +1) ) { return true; } return false; } /** * Returns the rightmost MIN_MATCH (5) characters in the network portion * in *reversed* order * * This can be used to do a database lookup against the column * that stores getStrippedReversed() * * Returns null if phoneNumber == null */ public static String toCallerIDMinMatch(String phoneNumber) { String np = extractNetworkPortion(phoneNumber); return internalGetStrippedReversed(np, MIN_MATCH); } /** * Returns the network portion reversed. * This string is intended to go into an index column for a * database lookup. * * Returns null if phoneNumber == null */ public static String getStrippedReversed(String phoneNumber) { String np = extractNetworkPortion(phoneNumber); if (np == null) return null; return internalGetStrippedReversed(np, np.length()); } /** * Returns the last numDigits of the reversed phone number * Returns null if np == null */ private static String internalGetStrippedReversed(String np, int numDigits) { if (np == null) return null; StringBuilder ret = new StringBuilder(numDigits); int length = np.length(); for (int i = length - 1, s = length ; i >= 0 && (s - i) <= numDigits ; i-- ) { char c = np.charAt(i); ret.append(c); } return ret.toString(); } /** * Basically: makes sure there's a + in front of a * TOA_International number * * Returns null if s == null */ public static String stringFromStringAndTOA(String s, int TOA) { if (s == null) return null; if (TOA == TOA_International && s.length() > 0 && s.charAt(0) != '+') { return "+" + s; } return s; } /** * Returns the TOA for the given dial string * Basically, returns TOA_International if there's a + prefix */ public static int toaFromString(String s) { if (s != null && s.length() > 0 && s.charAt(0) == '+') { return TOA_International; } return TOA_Unknown; } /** * Phone numbers are stored in "lookup" form in the database * as reversed strings to allow for caller ID lookup * * This method takes a phone number and makes a valid SQL "LIKE" * string that will match the lookup form * */ /** all of a up to len must be an international prefix or * separators/non-dialing digits */ private static boolean matchIntlPrefix(String a, int len) { /* '([^0-9*#+pwn]\+[^0-9*#+pwn] | [^0-9*#+pwn]0(0|11)[^0-9*#+pwn] )$' */ /* 0 1 2 3 45 */ int state = 0; for (int i = 0 ; i < len ; i++) { char c = a.charAt(i); switch (state) { case 0: if (c == '+') state = 1; else if (c == '0') state = 2; else if (isNonSeparator(c)) return false; break; case 2: if (c == '0') state = 3; else if (c == '1') state = 4; else if (isNonSeparator(c)) return false; break; case 4: if (c == '1') state = 5; else if (isNonSeparator(c)) return false; break; default: if (isNonSeparator(c)) return false; break; } } return state == 1 || state == 3 || state == 5; } /** * 3GPP TS 24.008 10.5.4.7 * Called Party BCD Number * * See Also TS 51.011 10.5.1 "dialing number/ssc string" * and TS 11.11 "10.3.1 EF adn (Abbreviated dialing numbers)" * * @param bytes the data buffer * @param offset should point to the TOA (aka. TON/NPI) octet after the length byte * @param length is the number of bytes including TOA byte * and must be at least 2 * * @return partial string on invalid decode * * FIXME(mkf) support alphanumeric address type * currently implemented in SMSMessage.getAddress() */ public static String calledPartyBCDToString (byte[] bytes, int offset, int length) { boolean prependPlus = false; StringBuilder ret = new StringBuilder(1 + length * 2); if (length < 2) { return ""; } if ((bytes[offset] & 0xff) == TOA_International) { prependPlus = true; } internalCalledPartyBCDFragmentToString( ret, bytes, offset + 1, length - 1); if (prependPlus && ret.length() == 0) { // If the only thing there is a prepended plus, return "" return ""; } if (prependPlus) { // This is an "international number" and should have // a plus prepended to the dialing number. But there // can also be Gsm MMI codes as defined in TS 22.030 6.5.2 // so we need to handle those also. // // http://web.telia.com/~u47904776/gsmkode.htm is a // has a nice list of some of these GSM codes. // // Examples are: // **21*+886988171479# // **21*8311234567# // *21# // #21# // *#21# // *31#+11234567890 // #31#+18311234567 // #31#8311234567 // 18311234567 // +18311234567# // +18311234567 // Odd ball cases that some phones handled // where there is no dialing number so they // append the "+" // *21#+ // **21#+ String retString = ret.toString(); Pattern p = Pattern.compile("(^[#*])(.*)([#*])(.*)(#)$"); Matcher m = p.matcher(retString); if (m.matches()) { if ("".equals(m.group(2))) { // Started with two [#*] ends with # // So no dialing number and we'll just // append a +, this handles **21#+ ret = new StringBuilder(); ret.append(m.group(1)); ret.append(m.group(3)); ret.append(m.group(4)); ret.append(m.group(5)); ret.append("+"); } else { // Starts with [#*] and ends with # // Assume group 4 is a dialing number // such as *21*+1234554# ret = new StringBuilder(); ret.append(m.group(1)); ret.append(m.group(2)); ret.append(m.group(3)); ret.append("+"); ret.append(m.group(4)); ret.append(m.group(5)); } } else { p = Pattern.compile("(^[#*])(.*)([#*])(.*)"); m = p.matcher(retString); if (m.matches()) { // Starts with [#*] and only one other [#*] // Assume the data after last [#*] is dialing // number (i.e. group 4) such as *31#+11234567890. // This also includes the odd ball *21#+ ret = new StringBuilder(); ret.append(m.group(1)); ret.append(m.group(2)); ret.append(m.group(3)); ret.append("+"); ret.append(m.group(4)); } else { // Does NOT start with [#*] just prepend '+' ret = new StringBuilder(); ret.append('+'); ret.append(retString); } } } return ret.toString(); } private static void internalCalledPartyBCDFragmentToString( StringBuilder sb, byte [] bytes, int offset, int length) { for (int i = offset ; i < length + offset ; i++) { byte b; char c; c = bcdToChar((byte)(bytes[i] & 0xf)); if (c == 0) { return; } sb.append(c); // FIXME(mkf) TS 23.040 9.1.2.3 says // "if a mobile receives 1111 in a position prior to // the last semi-octet then processing shall commense with // the next semi-octet and the intervening // semi-octet shall be ignored" // How does this jive with 24,008 10.5.4.7 b = (byte)((bytes[i] >> 4) & 0xf); if (b == 0xf && i + 1 == length + offset) { //ignore final 0xf break; } c = bcdToChar(b); if (c == 0) { return; } sb.append(c); } } /** * Like calledPartyBCDToString, but field does not start with a * TOA byte. For example: SIM ADN extension fields */ public static String calledPartyBCDFragmentToString(byte [] bytes, int offset, int length) { StringBuilder ret = new StringBuilder(length * 2); internalCalledPartyBCDFragmentToString(ret, bytes, offset, length); return ret.toString(); } /** returns 0 on invalid value */ private static char bcdToChar(byte b) { if (b < 0xa) { return (char)('0' + b); } else switch (b) { case 0xa: return '*'; case 0xb: return '#'; case 0xc: return PAUSE; case 0xd: return WILD; default: return 0; } } private static int charToBCD(char c) { if (c >= '0' && c <= '9') { return c - '0'; } else if (c == '*') { return 0xa; } else if (c == '#') { return 0xb; } else if (c == PAUSE) { return 0xc; } else if (c == WILD) { return 0xd; } else { throw new RuntimeException ("invalid char for BCD " + c); } } /** * Note: calls extractNetworkPortion(), so do not use for * SIM EF[ADN] style records * * Exceptions thrown if extractNetworkPortion(s).length() == 0 */ public static byte[] networkPortionToCalledPartyBCD(String s) { return numberToCalledPartyBCD(extractNetworkPortion(s)); } /** * Return true iff the network portion of <code>address</code> is, * as far as we can tell on the device, suitable for use as an SMS * destination address. */ public static boolean isWellFormedSmsAddress(String address) { String networkPortion = PhoneNumberUtils.extractNetworkPortion(address); return (!(networkPortion.equals("+") || TextUtils.isEmpty(networkPortion))) && isDialable(networkPortion); } public static boolean isGlobalPhoneNumber(String phoneNumber) { if (TextUtils.isEmpty(phoneNumber)) { return false; } Matcher match = GLOBAL_PHONE_NUMBER_PATTERN.matcher(phoneNumber); return match.matches(); } private static boolean isDialable(String address) { for (int i = 0, count = address.length(); i < count; i++) { if (!isDialable(address.charAt(i))) { return false; } } return true; } /** * Same as {@link #networkPortionToCalledPartyBCD}, but includes a * one-byte length prefix. */ public static byte[] networkPortionToCalledPartyBCDWithLength(String s) { return numberToCalledPartyBCDWithLength(extractNetworkPortion(s)); } /** * Convert a dialing number to BCD byte array * * @param number dialing number string * if the dialing number starts with '+', set to internationl TOA * @return BCD byte array */ public static byte[] numberToCalledPartyBCD(String number) { // The extra byte required for '+' is taken into consideration while calculating // length of ret. int size = (hasPlus(number) ? number.length() - 1 : number.length()); byte[] ret = new byte[(size + 1) / 2 + 1]; return numberToCalledPartyBCDHelper(ret, 0, number); } /** * Same as {@link #numberToCalledPartyBCD}, but includes a * one-byte length prefix. */ private static byte[] numberToCalledPartyBCDWithLength(String number) { // The extra byte required for '+' is taken into consideration while calculating // length of ret. int size = (hasPlus(number) ? number.length() - 1 : number.length()); int length = (size + 1) / 2 + 1; byte[] ret = new byte[length + 1]; ret[0] = (byte) (length & 0xff); return numberToCalledPartyBCDHelper(ret, 1, number); } private static boolean hasPlus(String s) { return s.indexOf('+') >= 0; } private static byte[] numberToCalledPartyBCDHelper(byte[] ret, int offset, String number) { if (hasPlus(number)) { number = number.replaceAll("\\+", ""); ret[offset] = (byte) TOA_International; } else { ret[offset] = (byte) TOA_Unknown; } int size = number.length(); int curChar = 0; int countFullBytes = ret.length - offset - 1 - ((size - curChar) & 1); for (int i = 1; i < 1 + countFullBytes; i++) { ret[offset + i] = (byte) ((charToBCD(number.charAt(curChar++))) | (charToBCD(number.charAt(curChar++))) << 4); } // The left-over octet for odd-length phone numbers should be // filled with 0xf. if (countFullBytes + offset < ret.length - 1) { ret[ret.length - 1] = (byte) (charToBCD(number.charAt(curChar)) | (0xf << 4)); } return ret; } /** all of 'a' up to len must match non-US trunk prefix ('0') */ private static boolean matchTrunkPrefix(String a, int len) { boolean found; found = false; for (int i = 0 ; i < len ; i++) { char c = a.charAt(i); if (c == '0' && !found) { found = true; } else if (isNonSeparator(c)) { return false; } } return found; } /** all of 'a' up to len must be a (+|00|011)country code) * We're fast and loose with the country code. Any \d{1,3} matches */ private static boolean matchIntlPrefixAndCC(String a, int len) { /* [^0-9*#+pwn]*(\+|0(0|11)\d\d?\d? [^0-9*#+pwn] $ */ /* 0 1 2 3 45 6 7 8 */ int state = 0; for (int i = 0 ; i < len ; i++ ) { char c = a.charAt(i); switch (state) { case 0: if (c == '+') state = 1; else if (c == '0') state = 2; else if (isNonSeparator(c)) return false; break; case 2: if (c == '0') state = 3; else if (c == '1') state = 4; else if (isNonSeparator(c)) return false; break; case 4: if (c == '1') state = 5; else if (isNonSeparator(c)) return false; break; case 1: case 3: case 5: if (isISODigit(c)) state = 6; else if (isNonSeparator(c)) return false; break; case 6: case 7: if (isISODigit(c)) state++; else if (isNonSeparator(c)) return false; break; default: if (isNonSeparator(c)) return false; } } return state == 6 || state == 7 || state == 8; } //================ Number formatting ========================= /** The current locale is unknown, look for a country code or don't format */ public static final int FORMAT_UNKNOWN = 0; /** NANP formatting */ public static final int FORMAT_NANP = 1; /** Japanese formatting */ public static final int FORMAT_JAPAN = 2; /** List of country codes for countries that use the NANP */ private static final String[] NANP_COUNTRIES = new String[] { "US", // United States "CA", // Canada "AS", // American Samoa "AI", // Anguilla "AG", // Antigua and Barbuda "BS", // Bahamas "BB", // Barbados "BM", // Bermuda "VG", // British Virgin Islands "KY", // Cayman Islands "DM", // Dominica "DO", // Dominican Republic "GD", // Grenada "GU", // Guam "JM", // Jamaica "PR", // Puerto Rico "MS", // Montserrat "NP", // Northern Mariana Islands "KN", // Saint Kitts and Nevis "LC", // Saint Lucia "VC", // Saint Vincent and the Grenadines "TT", // Trinidad and Tobago "TC", // Turks and Caicos Islands "VI", // U.S. Virgin Islands }; /** * Breaks the given number down and formats it according to the rules * for the country the number is from. * * @param source the phone number to format * @return a locally acceptable formatting of the input, or the raw input if * formatting rules aren't known for the number */ public static String formatNumber(String source) { SpannableStringBuilder text = new SpannableStringBuilder(source); formatNumber(text, getFormatTypeForLocale(Locale.getDefault())); return text.toString(); } /** * Returns the phone number formatting type for the given locale. * * @param locale The locale of interest, usually {@link Locale#getDefault()} * @return the formatting type for the given locale, or FORMAT_UNKNOWN if the formatting * rules are not known for the given locale */ public static int getFormatTypeForLocale(Locale locale) { String country = locale.getCountry(); // Check for the NANP countries int length = NANP_COUNTRIES.length; for (int i = 0; i < length; i++) { if (NANP_COUNTRIES[i].equals(country)) { return FORMAT_NANP; } } if (locale.equals(Locale.JAPAN)) { return FORMAT_JAPAN; } return FORMAT_UNKNOWN; } /** * Formats a phone number in-place. Currently only supports NANP formatting. * * @param text The number to be formatted, will be modified with the formatting * @param defaultFormattingType The default formatting rules to apply if the number does * not begin with +<country_code> */ public static void formatNumber(Editable text, int defaultFormattingType) { int formatType = defaultFormattingType; if (text.length() > 2 && text.charAt(0) == '+') { if (text.charAt(1) == '1') { formatType = FORMAT_NANP; } else if (text.length() >= 3 && text.charAt(1) == '8' && text.charAt(2) == '1') { formatType = FORMAT_JAPAN; } else { return; } } switch (formatType) { case FORMAT_NANP: formatNanpNumber(text); return; case FORMAT_JAPAN: formatJapaneseNumber(text); return; } } private static final int NANP_STATE_DIGIT = 1; private static final int NANP_STATE_PLUS = 2; private static final int NANP_STATE_ONE = 3; private static final int NANP_STATE_DASH = 4; /** * Formats a phone number in-place using the NANP formatting rules. Numbers will be formatted * as: * * <p><code> * xxx-xxxx * xxx-xxx-xxxx * 1-xxx-xxx-xxxx * +1-xxx-xxx-xxxx * </code></p> * * @param text the number to be formatted, will be modified with the formatting */ public static void formatNanpNumber(Editable text) { int length = text.length(); if (length > "+1-nnn-nnn-nnnn".length()) { // The string is too long to be formatted return; } CharSequence saved = text.subSequence(0, length); // Strip the dashes first, as we're going to add them back int p = 0; while (p < text.length()) { if (text.charAt(p) == '-') { text.delete(p, p + 1); } else { p++; } } length = text.length(); // When scanning the number we record where dashes need to be added, // if they're non-0 at the end of the scan the dashes will be added in // the proper places. int dashPositions[] = new int[3]; int numDashes = 0; int state = NANP_STATE_DIGIT; int numDigits = 0; for (int i = 0; i < length; i++) { char c = text.charAt(i); switch (c) { case '1': if (numDigits == 0 || state == NANP_STATE_PLUS) { state = NANP_STATE_ONE; break; } // fall through case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '0': if (state == NANP_STATE_PLUS) { // Only NANP number supported for now text.replace(0, length, saved); return; } else if (state == NANP_STATE_ONE) { // Found either +1 or 1, follow it up with a dash dashPositions[numDashes++] = i; } else if (state != NANP_STATE_DASH && (numDigits == 3 || numDigits == 6)) { // Found a digit that should be after a dash that isn't dashPositions[numDashes++] = i; } state = NANP_STATE_DIGIT; numDigits++; break; case '-': state = NANP_STATE_DASH; break; case '+': if (i == 0) { // Plus is only allowed as the first character state = NANP_STATE_PLUS; break; } // Fall through default: // Unknown character, bail on formatting text.replace(0, length, saved); return; } } if (numDigits == 7) { // With 7 digits we want xxx-xxxx, not xxx-xxx-x numDashes--; } // Actually put the dashes in place for (int i = 0; i < numDashes; i++) { int pos = dashPositions[i]; text.replace(pos + i, pos + i, "-"); } // Remove trailing dashes int len = text.length(); while (len > 0) { if (text.charAt(len - 1) == '-') { text.delete(len - 1, len); len--; } else { break; } } } /** * Formats a phone number in-place using the Japanese formatting rules. * Numbers will be formatted as: * * <p><code> * 03-xxxx-xxxx * 090-xxxx-xxxx * 0120-xxx-xxx * +81-3-xxxx-xxxx * +81-90-xxxx-xxxx * </code></p> * * @param text the number to be formatted, will be modified with * the formatting */ public static void formatJapaneseNumber(Editable text) { JapanesePhoneNumberFormatter.format(text); } // Three and four digit phone numbers for either special services // or from the network (eg carrier-originated SMS messages) should // not match static final int MIN_MATCH = 5; /** * isEmergencyNumber: checks a given number against the list of * emergency numbers provided by the RIL and SIM card. * * @param number the number to look up. * @return if the number is in the list of emergency numbers * listed in the ril / sim, then return true, otherwise false. */ public static boolean isEmergencyNumber(String number) { // Strip the separators from the number before comparing it // to the list. number = extractNetworkPortion(number); // retrieve the list of emergency numbers String numbers = SystemProperties.get("ro.ril.ecclist"); if (!TextUtils.isEmpty(numbers)) { // searches through the comma-separated list for a match, // return true if one is found. for (String emergencyNum : numbers.split(",")) { if (emergencyNum.equals(number)) { return true; } } // no matches found against the list! return false; } //no ecclist system property, so use our own list. return (number.equals("112") || number.equals("911")); } /** * Translates any alphabetic letters (i.e. [A-Za-z]) in the * specified phone number into the equivalent numeric digits, * according to the phone keypad letter mapping described in * ITU E.161 and ISO/IEC 9995-8. * * @return the input string, with alpha letters converted to numeric * digits using the phone keypad letter mapping. For example, * an input of "1-800-GOOG-411" will return "1-800-4664-411". */ public static String convertKeypadLettersToDigits(String input) { if (input == null) { return input; } int len = input.length(); if (len == 0) { return input; } char[] out = input.toCharArray(); for (int i = 0; i < len; i++) { char c = out[i]; // If this char isn't in KEYPAD_MAP at all, just leave it alone. out[i] = (char) KEYPAD_MAP.get(c, c); } return new String(out); } /** * The phone keypad letter mapping (see ITU E.161 or ISO/IEC 9995-8.) * TODO: This should come from a resource. */ private static final SparseIntArray KEYPAD_MAP = new SparseIntArray(); static { KEYPAD_MAP.put('a', '2'); KEYPAD_MAP.put('b', '2'); KEYPAD_MAP.put('c', '2'); KEYPAD_MAP.put('A', '2'); KEYPAD_MAP.put('B', '2'); KEYPAD_MAP.put('C', '2'); KEYPAD_MAP.put('d', '3'); KEYPAD_MAP.put('e', '3'); KEYPAD_MAP.put('f', '3'); KEYPAD_MAP.put('D', '3'); KEYPAD_MAP.put('E', '3'); KEYPAD_MAP.put('F', '3'); KEYPAD_MAP.put('g', '4'); KEYPAD_MAP.put('h', '4'); KEYPAD_MAP.put('i', '4'); KEYPAD_MAP.put('G', '4'); KEYPAD_MAP.put('H', '4'); KEYPAD_MAP.put('I', '4'); KEYPAD_MAP.put('j', '5'); KEYPAD_MAP.put('k', '5'); KEYPAD_MAP.put('l', '5'); KEYPAD_MAP.put('J', '5'); KEYPAD_MAP.put('K', '5'); KEYPAD_MAP.put('L', '5'); KEYPAD_MAP.put('m', '6'); KEYPAD_MAP.put('n', '6'); KEYPAD_MAP.put('o', '6'); KEYPAD_MAP.put('M', '6'); KEYPAD_MAP.put('N', '6'); KEYPAD_MAP.put('O', '6'); KEYPAD_MAP.put('p', '7'); KEYPAD_MAP.put('q', '7'); KEYPAD_MAP.put('r', '7'); KEYPAD_MAP.put('s', '7'); KEYPAD_MAP.put('P', '7'); KEYPAD_MAP.put('Q', '7'); KEYPAD_MAP.put('R', '7'); KEYPAD_MAP.put('S', '7'); KEYPAD_MAP.put('t', '8'); KEYPAD_MAP.put('u', '8'); KEYPAD_MAP.put('v', '8'); KEYPAD_MAP.put('T', '8'); KEYPAD_MAP.put('U', '8'); KEYPAD_MAP.put('V', '8'); KEYPAD_MAP.put('w', '9'); KEYPAD_MAP.put('x', '9'); KEYPAD_MAP.put('y', '9'); KEYPAD_MAP.put('z', '9'); KEYPAD_MAP.put('W', '9'); KEYPAD_MAP.put('X', '9'); KEYPAD_MAP.put('Y', '9'); KEYPAD_MAP.put('Z', '9'); } }