PhoneNumberUtils.java revision 54b6cfa9a9e5b861a9930af873580d6dc20f773c
1/* 2 * Copyright (C) 2006 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 17package android.telephony; 18 19import android.content.Context; 20import android.content.Intent; 21import android.database.Cursor; 22import android.net.Uri; 23import android.os.SystemProperties; 24import android.provider.Contacts; 25import android.text.Editable; 26import android.text.SpannableStringBuilder; 27import android.text.TextUtils; 28import android.util.SparseIntArray; 29 30import java.util.Locale; 31import java.util.regex.Matcher; 32import java.util.regex.Pattern; 33 34/** 35 * Various utilities for dealing with phone number strings. 36 */ 37public class PhoneNumberUtils 38{ 39 /* 40 * Special characters 41 * 42 * (See "What is a phone number?" doc) 43 * 'p' --- GSM pause character, same as comma 44 * 'n' --- GSM wild character 45 * 'w' --- GSM wait character 46 */ 47 public static final char PAUSE = ','; 48 public static final char WAIT = ';'; 49 public static final char WILD = 'N'; 50 51 /* 52 * TOA = TON + NPI 53 * See TS 24.008 section 10.5.4.7 for details. 54 * These are the only really useful TOA values 55 */ 56 public static final int TOA_International = 0x91; 57 public static final int TOA_Unknown = 0x81; 58 59 /* 60 * global-phone-number = ["+"] 1*( DIGIT / written-sep ) 61 * written-sep = ("-"/".") 62 */ 63 private static final Pattern GLOBAL_PHONE_NUMBER_PATTERN = 64 Pattern.compile("[\\+]?[0-9.-]+"); 65 66 /** True if c is ISO-LATIN characters 0-9 */ 67 public static boolean 68 isISODigit (char c) { 69 return c >= '0' && c <= '9'; 70 } 71 72 /** True if c is ISO-LATIN characters 0-9, *, # */ 73 public final static boolean 74 is12Key(char c) { 75 return (c >= '0' && c <= '9') || c == '*' || c == '#'; 76 } 77 78 /** True if c is ISO-LATIN characters 0-9, *, # , +, WILD */ 79 public final static boolean 80 isDialable(char c) { 81 return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+' || c == WILD; 82 } 83 84 /** True if c is ISO-LATIN characters 0-9, *, # , + (no WILD) */ 85 public final static boolean 86 isReallyDialable(char c) { 87 return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+'; 88 } 89 90 /** True if c is ISO-LATIN characters 0-9, *, # , +, WILD, WAIT, PAUSE */ 91 public final static boolean 92 isNonSeparator(char c) { 93 return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+' 94 || c == WILD || c == WAIT || c == PAUSE; 95 } 96 97 /** This any anything to the right of this char is part of the 98 * post-dial string (eg this is PAUSE or WAIT) 99 */ 100 public final static boolean 101 isStartsPostDial (char c) { 102 return c == PAUSE || c == WAIT; 103 } 104 105 /** Extracts the phone number from an Intent. 106 * 107 * @param intent the intent to get the number of 108 * @param context a context to use for database access 109 * 110 * @return the phone number that would be called by the intent, or 111 * <code>null</code> if the number cannot be found. 112 */ 113 public static String getNumberFromIntent(Intent intent, Context context) { 114 String number = null; 115 116 Uri uri = intent.getData(); 117 String scheme = uri.getScheme(); 118 119 if (scheme.equals("tel")) { 120 return uri.getSchemeSpecificPart(); 121 } 122 123 if (scheme.equals("voicemail")) { 124 return TelephonyManager.getDefault().getVoiceMailNumber(); 125 } 126 127 if (context == null) { 128 return null; 129 } 130 131 String type = intent.resolveType(context); 132 133 Cursor c = context.getContentResolver().query( 134 uri, new String[]{ Contacts.People.Phones.NUMBER }, 135 null, null, null); 136 if (c != null) { 137 try { 138 if (c.moveToFirst()) { 139 number = c.getString( 140 c.getColumnIndex(Contacts.People.Phones.NUMBER)); 141 } 142 } finally { 143 c.close(); 144 } 145 } 146 147 return number; 148 } 149 150 /** Extracts the network address portion and canonicalizes 151 * (filters out separators.) 152 * Network address portion is everything up to DTMF control digit 153 * separators (pause or wait), but without non-dialable characters. 154 * 155 * Please note that the GSM wild character is allowed in the result. 156 * This must be resolved before dialing. 157 * 158 * Allows + only in the first position in the result string. 159 * 160 * Returns null if phoneNumber == null 161 */ 162 public static String 163 extractNetworkPortion(String phoneNumber) { 164 if (phoneNumber == null) { 165 return null; 166 } 167 168 int len = phoneNumber.length(); 169 StringBuilder ret = new StringBuilder(len); 170 boolean firstCharAdded = false; 171 172 for (int i = 0; i < len; i++) { 173 char c = phoneNumber.charAt(i); 174 if (isDialable(c) && (c != '+' || !firstCharAdded)) { 175 firstCharAdded = true; 176 ret.append(c); 177 } else if (isStartsPostDial (c)) { 178 break; 179 } 180 } 181 182 return ret.toString(); 183 } 184 185 /** 186 * Strips separators from a phone number string. 187 * @param phoneNumber phone number to strip. 188 * @return phone string stripped of separators. 189 */ 190 public static String stripSeparators(String phoneNumber) { 191 if (phoneNumber == null) { 192 return null; 193 } 194 int len = phoneNumber.length(); 195 StringBuilder ret = new StringBuilder(len); 196 197 for (int i = 0; i < len; i++) { 198 char c = phoneNumber.charAt(i); 199 if (isNonSeparator(c)) { 200 ret.append(c); 201 } 202 } 203 204 return ret.toString(); 205 } 206 207 /** or -1 if both are negative */ 208 static private int 209 minPositive (int a, int b) { 210 if (a >= 0 && b >= 0) { 211 return (a < b) ? a : b; 212 } else if (a >= 0) { /* && b < 0 */ 213 return a; 214 } else if (b >= 0) { /* && a < 0 */ 215 return b; 216 } else { /* a < 0 && b < 0 */ 217 return -1; 218 } 219 } 220 221 /** index of the last character of the network portion 222 * (eg anything after is a post-dial string) 223 */ 224 static private int 225 indexOfLastNetworkChar(String a) { 226 int pIndex, wIndex; 227 int origLength; 228 int trimIndex; 229 230 origLength = a.length(); 231 232 pIndex = a.indexOf(PAUSE); 233 wIndex = a.indexOf(WAIT); 234 235 trimIndex = minPositive(pIndex, wIndex); 236 237 if (trimIndex < 0) { 238 return origLength - 1; 239 } else { 240 return trimIndex - 1; 241 } 242 } 243 244 /** 245 * Extracts the post-dial sequence of DTMF control digits, pauses, and 246 * waits. Strips separators. This string may be empty, but will not be null 247 * unless phoneNumber == null. 248 * 249 * Returns null if phoneNumber == null 250 */ 251 252 public static String 253 extractPostDialPortion(String phoneNumber) { 254 if (phoneNumber == null) return null; 255 256 int trimIndex; 257 StringBuilder ret = new StringBuilder(); 258 259 trimIndex = indexOfLastNetworkChar (phoneNumber); 260 261 for (int i = trimIndex + 1, s = phoneNumber.length() 262 ; i < s; i++ 263 ) { 264 char c = phoneNumber.charAt(i); 265 if (isNonSeparator(c)) { 266 ret.append(c); 267 } 268 } 269 270 return ret.toString(); 271 } 272 273 /** 274 * Compare phone numbers a and b, return true if they're identical 275 * enough for caller ID purposes. 276 * 277 * - Compares from right to left 278 * - requires MIN_MATCH (5) characters to match 279 * - handles common trunk prefixes and international prefixes 280 * (basically, everything except the Russian trunk prefix) 281 * 282 * Tolerates nulls 283 */ 284 public static boolean 285 compare(String a, String b) { 286 int ia, ib; 287 int matched; 288 289 if (a == null || b == null) return a == b; 290 291 if (a.length() == 0 || b.length() == 0) { 292 return false; 293 } 294 295 ia = indexOfLastNetworkChar (a); 296 ib = indexOfLastNetworkChar (b); 297 matched = 0; 298 299 while (ia >= 0 && ib >=0) { 300 char ca, cb; 301 boolean skipCmp = false; 302 303 ca = a.charAt(ia); 304 305 if (!isDialable(ca)) { 306 ia--; 307 skipCmp = true; 308 } 309 310 cb = b.charAt(ib); 311 312 if (!isDialable(cb)) { 313 ib--; 314 skipCmp = true; 315 } 316 317 if (!skipCmp) { 318 if (cb != ca && ca != WILD && cb != WILD) { 319 break; 320 } 321 ia--; ib--; matched++; 322 } 323 } 324 325 if (matched < MIN_MATCH) { 326 int aLen = a.length(); 327 328 // if the input strings match, but their lengths < MIN_MATCH, 329 // treat them as equal. 330 if (aLen == b.length() && aLen == matched) { 331 return true; 332 } 333 return false; 334 } 335 336 // At least one string has matched completely; 337 if (matched >= MIN_MATCH && (ia < 0 || ib < 0)) { 338 return true; 339 } 340 341 /* 342 * Now, what remains must be one of the following for a 343 * match: 344 * 345 * - a '+' on one and a '00' or a '011' on the other 346 * - a '0' on one and a (+,00)<country code> on the other 347 * (for this, a '0' and a '00' prefix would have succeeded above) 348 */ 349 350 if (matchIntlPrefix(a, ia + 1) 351 && matchIntlPrefix (b, ib +1) 352 ) { 353 return true; 354 } 355 356 if (matchTrunkPrefix(a, ia + 1) 357 && matchIntlPrefixAndCC(b, ib +1) 358 ) { 359 return true; 360 } 361 362 if (matchTrunkPrefix(b, ib + 1) 363 && matchIntlPrefixAndCC(a, ia +1) 364 ) { 365 return true; 366 } 367 368 return false; 369 } 370 371 /** 372 * Returns the rightmost MIN_MATCH (5) characters in the network portion 373 * in *reversed* order 374 * 375 * This can be used to do a database lookup against the column 376 * that stores getStrippedReversed() 377 * 378 * Returns null if phoneNumber == null 379 */ 380 public static String 381 toCallerIDMinMatch(String phoneNumber) { 382 String np = extractNetworkPortion(phoneNumber); 383 return internalGetStrippedReversed(np, MIN_MATCH); 384 } 385 386 /** 387 * Returns the network portion reversed. 388 * This string is intended to go into an index column for a 389 * database lookup. 390 * 391 * Returns null if phoneNumber == null 392 */ 393 public static String 394 getStrippedReversed(String phoneNumber) { 395 String np = extractNetworkPortion(phoneNumber); 396 397 if (np == null) return null; 398 399 return internalGetStrippedReversed(np, np.length()); 400 } 401 402 /** 403 * Returns the last numDigits of the reversed phone number 404 * Returns null if np == null 405 */ 406 private static String 407 internalGetStrippedReversed(String np, int numDigits) { 408 if (np == null) return null; 409 410 StringBuilder ret = new StringBuilder(numDigits); 411 int length = np.length(); 412 413 for (int i = length - 1, s = length 414 ; i >= 0 && (s - i) <= numDigits ; i-- 415 ) { 416 char c = np.charAt(i); 417 418 ret.append(c); 419 } 420 421 return ret.toString(); 422 } 423 424 /** 425 * Basically: makes sure there's a + in front of a 426 * TOA_International number 427 * 428 * Returns null if s == null 429 */ 430 public static String 431 stringFromStringAndTOA(String s, int TOA) { 432 if (s == null) return null; 433 434 if (TOA == TOA_International && s.length() > 0 && s.charAt(0) != '+') { 435 return "+" + s; 436 } 437 438 return s; 439 } 440 441 /** 442 * Returns the TOA for the given dial string 443 * Basically, returns TOA_International if there's a + prefix 444 */ 445 446 public static int 447 toaFromString(String s) { 448 if (s != null && s.length() > 0 && s.charAt(0) == '+') { 449 return TOA_International; 450 } 451 452 return TOA_Unknown; 453 } 454 455 /** 456 * Phone numbers are stored in "lookup" form in the database 457 * as reversed strings to allow for caller ID lookup 458 * 459 * This method takes a phone number and makes a valid SQL "LIKE" 460 * string that will match the lookup form 461 * 462 */ 463 /** all of a up to len must be an international prefix or 464 * separators/non-dialing digits 465 */ 466 private static boolean 467 matchIntlPrefix(String a, int len) { 468 /* '([^0-9*#+pwn]\+[^0-9*#+pwn] | [^0-9*#+pwn]0(0|11)[^0-9*#+pwn] )$' */ 469 /* 0 1 2 3 45 */ 470 471 int state = 0; 472 for (int i = 0 ; i < len ; i++) { 473 char c = a.charAt(i); 474 475 switch (state) { 476 case 0: 477 if (c == '+') state = 1; 478 else if (c == '0') state = 2; 479 else if (isNonSeparator(c)) return false; 480 break; 481 482 case 2: 483 if (c == '0') state = 3; 484 else if (c == '1') state = 4; 485 else if (isNonSeparator(c)) return false; 486 break; 487 488 case 4: 489 if (c == '1') state = 5; 490 else if (isNonSeparator(c)) return false; 491 break; 492 493 default: 494 if (isNonSeparator(c)) return false; 495 break; 496 497 } 498 } 499 500 return state == 1 || state == 3 || state == 5; 501 } 502 503 /** 504 * 3GPP TS 24.008 10.5.4.7 505 * Called Party BCD Number 506 * 507 * See Also TS 51.011 10.5.1 "dialing number/ssc string" 508 * 509 * @param bytes the data buffer 510 * @param offset should point to the TOI/NPI octet after the length byte 511 * @param length is the number of bytes including TOA byte 512 * and must be at least 2 513 * 514 * @return partial string on invalid decode 515 * 516 * FIXME(mkf) support alphanumeric address type 517 * currently implemented in SMSMessage.getAddress() 518 */ 519 public static String 520 calledPartyBCDToString (byte[] bytes, int offset, int length) { 521 boolean prependedPlus = false; 522 StringBuilder ret = new StringBuilder(1 + length * 2); 523 524 if (length < 2) { 525 return ""; 526 } 527 528 if ((bytes[offset] & 0xff) == TOA_International) { 529 ret.append("+"); 530 prependedPlus = true; 531 } 532 533 internalCalledPartyBCDFragmentToString( 534 ret, bytes, offset + 1, length - 1); 535 536 if (prependedPlus && ret.length() == 1) { 537 // If the only thing there is a prepended plus, return "" 538 return ""; 539 } 540 541 return ret.toString(); 542 } 543 544 private static void 545 internalCalledPartyBCDFragmentToString( 546 StringBuilder sb, byte [] bytes, int offset, int length) { 547 for (int i = offset ; i < length + offset ; i++) { 548 byte b; 549 char c; 550 551 c = bcdToChar((byte)(bytes[i] & 0xf)); 552 553 if (c == 0) { 554 return; 555 } 556 sb.append(c); 557 558 // FIXME(mkf) TS 23.040 9.1.2.3 says 559 // "if a mobile receives 1111 in a position prior to 560 // the last semi-octet then processing shall commense with 561 // the next semi-octet and the intervening 562 // semi-octet shall be ignored" 563 // How does this jive with 24,008 10.5.4.7 564 565 b = (byte)((bytes[i] >> 4) & 0xf); 566 567 if (b == 0xf && i + 1 == length + offset) { 568 //ignore final 0xf 569 break; 570 } 571 572 c = bcdToChar(b); 573 if (c == 0) { 574 return; 575 } 576 577 sb.append(c); 578 } 579 580 } 581 582 /** 583 * Like calledPartyBCDToString, but field does not start with a 584 * TOA byte. For example: SIM ADN extension fields 585 */ 586 587 public static String 588 calledPartyBCDFragmentToString(byte [] bytes, int offset, int length) { 589 StringBuilder ret = new StringBuilder(length * 2); 590 591 internalCalledPartyBCDFragmentToString(ret, bytes, offset, length); 592 593 return ret.toString(); 594 } 595 596 /** returns 0 on invalid value */ 597 private static char 598 bcdToChar(byte b) { 599 if (b < 0xa) { 600 return (char)('0' + b); 601 } else switch (b) { 602 case 0xa: return '*'; 603 case 0xb: return '#'; 604 case 0xc: return PAUSE; 605 case 0xd: return WILD; 606 607 default: return 0; 608 } 609 } 610 611 private static int 612 charToBCD(char c) { 613 if (c >= '0' && c <= '9') { 614 return c - '0'; 615 } else if (c == '*') { 616 return 0xa; 617 } else if (c == '#') { 618 return 0xb; 619 } else if (c == PAUSE) { 620 return 0xc; 621 } else if (c == WILD) { 622 return 0xd; 623 } else { 624 throw new RuntimeException ("invalid char for BCD " + c); 625 } 626 } 627 628 /** 629 * Note: calls extractNetworkPortion(), so do not use for 630 * SIM EF[ADN] style records 631 * 632 * Exceptions thrown if extractNetworkPortion(s).length() == 0 633 */ 634 public static byte[] 635 networkPortionToCalledPartyBCD(String s) { 636 return numberToCalledPartyBCD(extractNetworkPortion(s)); 637 } 638 639 /** 640 * Return true iff the network portion of <code>address</code> is, 641 * as far as we can tell on the device, suitable for use as an SMS 642 * destination address. 643 */ 644 public static boolean isWellFormedSmsAddress(String address) { 645 String networkPortion = 646 PhoneNumberUtils.extractNetworkPortion(address); 647 648 return (!(networkPortion.equals("+") 649 || TextUtils.isEmpty(networkPortion))) 650 && isDialable(networkPortion); 651 } 652 653 public static boolean isGlobalPhoneNumber(String phoneNumber) { 654 if (TextUtils.isEmpty(phoneNumber)) { 655 return false; 656 } 657 658 Matcher match = GLOBAL_PHONE_NUMBER_PATTERN.matcher(phoneNumber); 659 return match.matches(); 660 } 661 662 private static boolean isDialable(String address) { 663 for (int i = 0, count = address.length(); i < count; i++) { 664 if (!isDialable(address.charAt(i))) { 665 return false; 666 } 667 } 668 return true; 669 } 670 671 /** 672 * Same as {@link #networkPortionToCalledPartyBCD}, but includes a 673 * one-byte length prefix. 674 */ 675 public static byte[] 676 networkPortionToCalledPartyBCDWithLength(String s) { 677 return numberToCalledPartyBCDWithLength(extractNetworkPortion(s)); 678 } 679 680 /** 681 * Convert a dialing number to BCD byte array 682 * 683 * @param number dialing number string 684 * if the dialing number starts with '+', set to internationl TOA 685 * @return BCD byte array 686 */ 687 public static byte[] 688 numberToCalledPartyBCD(String number) { 689 // The extra byte required for '+' is taken into consideration while calculating 690 // length of ret. 691 int size = ((number.charAt(0) == '+') ? number.length() - 1 : number.length()); 692 byte[] ret = new byte[(size + 1) / 2 + 1]; 693 694 return numberToCalledPartyBCDHelper(ret, 0, number); 695 } 696 697 /** 698 * Same as {@link #numberToCalledPartyBCD}, but includes a 699 * one-byte length prefix. 700 */ 701 private static byte[] 702 numberToCalledPartyBCDWithLength(String number) { 703 // The extra byte required for '+' is taken into consideration while calculating 704 // length of ret. 705 int size = ((number.charAt(0) == '+') ? number.length() - 1 : number.length()); 706 int length = (size + 1) / 2 + 1; 707 byte[] ret = new byte[length + 1]; 708 709 ret[0] = (byte) (length & 0xff); 710 return numberToCalledPartyBCDHelper(ret, 1, number); 711 } 712 713 private static byte[] 714 numberToCalledPartyBCDHelper(byte[] ret, int offset, String number) { 715 int size; 716 int curChar; 717 718 size = number.length(); 719 720 if (number.charAt(0) == '+') { 721 curChar = 1; 722 ret[offset] = (byte) TOA_International; 723 } else { 724 curChar = 0; 725 ret[offset] = (byte) TOA_Unknown; 726 } 727 728 int countFullBytes = ret.length - offset - 1 - ((size - curChar) & 1); 729 for (int i = 1; i < 1 + countFullBytes; i++) { 730 ret[offset + i] 731 = (byte) ((charToBCD(number.charAt(curChar++))) 732 | (charToBCD(number.charAt(curChar++))) << 4); 733 } 734 735 // The left-over octet for odd-length phone numbers should be 736 // filled with 0xf. 737 if (countFullBytes + offset < ret.length - 1) { 738 ret[ret.length - 1] 739 = (byte) (charToBCD(number.charAt(curChar)) 740 | (0xf << 4)); 741 } 742 return ret; 743 } 744 745 /** all of 'a' up to len must match non-US trunk prefix ('0') */ 746 private static boolean 747 matchTrunkPrefix(String a, int len) { 748 boolean found; 749 750 found = false; 751 752 for (int i = 0 ; i < len ; i++) { 753 char c = a.charAt(i); 754 755 if (c == '0' && !found) { 756 found = true; 757 } else if (isNonSeparator(c)) { 758 return false; 759 } 760 } 761 762 return found; 763 } 764 765 /** all of 'a' up to len must be a (+|00|011)country code) 766 * We're fast and loose with the country code. Any \d{1,3} matches */ 767 private static boolean 768 matchIntlPrefixAndCC(String a, int len) { 769 /* [^0-9*#+pwn]*(\+|0(0|11)\d\d?\d? [^0-9*#+pwn] $ */ 770 /* 0 1 2 3 45 6 7 8 */ 771 772 int state = 0; 773 for (int i = 0 ; i < len ; i++ ) { 774 char c = a.charAt(i); 775 776 switch (state) { 777 case 0: 778 if (c == '+') state = 1; 779 else if (c == '0') state = 2; 780 else if (isNonSeparator(c)) return false; 781 break; 782 783 case 2: 784 if (c == '0') state = 3; 785 else if (c == '1') state = 4; 786 else if (isNonSeparator(c)) return false; 787 break; 788 789 case 4: 790 if (c == '1') state = 5; 791 else if (isNonSeparator(c)) return false; 792 break; 793 794 case 1: 795 case 3: 796 case 5: 797 if (isISODigit(c)) state = 6; 798 else if (isNonSeparator(c)) return false; 799 break; 800 801 case 6: 802 case 7: 803 if (isISODigit(c)) state++; 804 else if (isNonSeparator(c)) return false; 805 break; 806 807 default: 808 if (isNonSeparator(c)) return false; 809 } 810 } 811 812 return state == 6 || state == 7 || state == 8; 813 } 814 815 //================ Number formatting ========================= 816 817 /** The current locale is unknown, look for a country code or don't format */ 818 public static final int FORMAT_UNKNOWN = 0; 819 /** NANP formatting */ 820 public static final int FORMAT_NANP = 1; 821 822 /** List of country codes for countries that use the NANP */ 823 private static final String[] NANP_COUNTRIES = new String[] { 824 "US", // United States 825 "CA", // Canada 826 "AS", // American Samoa 827 "AI", // Anguilla 828 "AG", // Antigua and Barbuda 829 "BS", // Bahamas 830 "BB", // Barbados 831 "BM", // Bermuda 832 "VG", // British Virgin Islands 833 "KY", // Cayman Islands 834 "DM", // Dominica 835 "DO", // Dominican Republic 836 "GD", // Grenada 837 "GU", // Guam 838 "JM", // Jamaica 839 "PR", // Puerto Rico 840 "MS", // Montserrat 841 "NP", // Northern Mariana Islands 842 "KN", // Saint Kitts and Nevis 843 "LC", // Saint Lucia 844 "VC", // Saint Vincent and the Grenadines 845 "TT", // Trinidad and Tobago 846 "TC", // Turks and Caicos Islands 847 "VI", // U.S. Virgin Islands 848 }; 849 850 /** 851 * Breaks the given number down and formats it according to the rules 852 * for the country the number is from. 853 * 854 * @param source the phone number to format 855 * @return a locally acceptable formatting of the input, or the raw input if 856 * formatting rules aren't known for the number 857 */ 858 public static String formatNumber(String source) { 859 SpannableStringBuilder text = new SpannableStringBuilder(source); 860 formatNumber(text, getFormatTypeForLocale(Locale.getDefault())); 861 return text.toString(); 862 } 863 864 /** 865 * Returns the phone number formatting type for the given locale. 866 * 867 * @param locale The locale of interest, usually {@link Locale#getDefault()} 868 * @return the formatting type for the given locale, or FORMAT_UNKNOWN if the formatting 869 * rules are not known for the given locale 870 */ 871 public static int getFormatTypeForLocale(Locale locale) { 872 String country = locale.getCountry(); 873 874 // Check for the NANP countries 875 int length = NANP_COUNTRIES.length; 876 for (int i = 0; i < length; i++) { 877 if (NANP_COUNTRIES[i].equals(country)) { 878 return FORMAT_NANP; 879 } 880 } 881 return FORMAT_UNKNOWN; 882 } 883 884 /** 885 * Formats a phone number in-place. Currently only supports NANP formatting. 886 * 887 * @param text The number to be formatted, will be modified with the formatting 888 * @param defaultFormattingType The default formatting rules to apply if the number does 889 * not begin with +<country_code> 890 */ 891 public static void formatNumber(Editable text, int defaultFormattingType) { 892 int formatType = defaultFormattingType; 893 894 // This only handles +1 for now 895 if (text.length() > 2 && text.charAt(0) == '+') { 896 if (text.charAt(1) == '1') { 897 formatType = FORMAT_NANP; 898 } else { 899 return; 900 } 901 } 902 903 switch (formatType) { 904 case FORMAT_NANP: 905 formatNanpNumber(text); 906 return; 907 } 908 } 909 910 private static final int NANP_STATE_DIGIT = 1; 911 private static final int NANP_STATE_PLUS = 2; 912 private static final int NANP_STATE_ONE = 3; 913 private static final int NANP_STATE_DASH = 4; 914 915 /** 916 * Formats a phone number in-place using the NANP formatting rules. Numbers will be formatted 917 * as: 918 * 919 * <p><code> 920 * xxx-xxxx 921 * xxx-xxx-xxxx 922 * 1-xxx-xxx-xxxx 923 * +1-xxx-xxx-xxxx 924 * </code></p> 925 * 926 * @param text the number to be formatted, will be modified with the formatting 927 */ 928 public static void formatNanpNumber(Editable text) { 929 int length = text.length(); 930 if (length > "+1-nnn-nnn-nnnn".length()) { 931 // The string is too long to be formatted 932 return; 933 } 934 CharSequence saved = text.subSequence(0, length); 935 936 // Strip the dashes first, as we're going to add them back 937 int p = 0; 938 while (p < text.length()) { 939 if (text.charAt(p) == '-') { 940 text.delete(p, p + 1); 941 } else { 942 p++; 943 } 944 } 945 length = text.length(); 946 947 // When scanning the number we record where dashes need to be added, 948 // if they're non-0 at the end of the scan the dashes will be added in 949 // the proper places. 950 int dashPositions[] = new int[3]; 951 int numDashes = 0; 952 953 int state = NANP_STATE_DIGIT; 954 int numDigits = 0; 955 for (int i = 0; i < length; i++) { 956 char c = text.charAt(i); 957 switch (c) { 958 case '1': 959 if (numDigits == 0 || state == NANP_STATE_PLUS) { 960 state = NANP_STATE_ONE; 961 break; 962 } 963 // fall through 964 case '2': 965 case '3': 966 case '4': 967 case '5': 968 case '6': 969 case '7': 970 case '8': 971 case '9': 972 case '0': 973 if (state == NANP_STATE_PLUS) { 974 // Only NANP number supported for now 975 text.replace(0, length, saved); 976 return; 977 } else if (state == NANP_STATE_ONE) { 978 // Found either +1 or 1, follow it up with a dash 979 dashPositions[numDashes++] = i; 980 } else if (state != NANP_STATE_DASH && (numDigits == 3 || numDigits == 6)) { 981 // Found a digit that should be after a dash that isn't 982 dashPositions[numDashes++] = i; 983 } 984 state = NANP_STATE_DIGIT; 985 numDigits++; 986 break; 987 988 case '-': 989 state = NANP_STATE_DASH; 990 break; 991 992 case '+': 993 if (i == 0) { 994 // Plus is only allowed as the first character 995 state = NANP_STATE_PLUS; 996 break; 997 } 998 // Fall through 999 default: 1000 // Unknown character, bail on formatting 1001 text.replace(0, length, saved); 1002 return; 1003 } 1004 } 1005 1006 if (numDigits == 7) { 1007 // With 7 digits we want xxx-xxxx, not xxx-xxx-x 1008 numDashes--; 1009 } 1010 1011 // Actually put the dashes in place 1012 for (int i = 0; i < numDashes; i++) { 1013 int pos = dashPositions[i]; 1014 text.replace(pos + i, pos + i, "-"); 1015 } 1016 1017 // Remove trailing dashes 1018 int len = text.length(); 1019 while (len > 0) { 1020 if (text.charAt(len - 1) == '-') { 1021 text.delete(len - 1, len); 1022 len--; 1023 } else { 1024 break; 1025 } 1026 } 1027 } 1028 1029 // Three and four digit phone numbers for either special services 1030 // or from the network (eg carrier-originated SMS messages) should 1031 // not match 1032 static final int MIN_MATCH = 5; 1033 1034 /** 1035 * isEmergencyNumber: checks a given number against the list of 1036 * emergency numbers provided by the RIL and SIM card. 1037 * 1038 * @param number the number to look up. 1039 * @return if the number is in the list of emergency numbers 1040 * listed in the ril / sim, then return true, otherwise false. 1041 */ 1042 public static boolean isEmergencyNumber(String number) { 1043 // Strip the separators from the number before comparing it 1044 // to the list. 1045 number = extractNetworkPortion(number); 1046 1047 // retrieve the list of emergency numbers 1048 String numbers = SystemProperties.get("ro.ril.ecclist"); 1049 1050 if (!TextUtils.isEmpty(numbers)) { 1051 // searches through the comma-separated list for a match, 1052 // return true if one is found. 1053 for (String emergencyNum : numbers.split(",")) { 1054 if (emergencyNum.equals(number)) { 1055 return true; 1056 } 1057 } 1058 // no matches found against the list! 1059 return false; 1060 } 1061 1062 //no ecclist system property, so use our own list. 1063 return (number.equals("112") || number.equals("911")); 1064 } 1065 1066 /** 1067 * Translates any alphabetic letters (i.e. [A-Za-z]) in the 1068 * specified phone number into the equivalent numeric digits, 1069 * according to the phone keypad letter mapping described in 1070 * ITU E.161 and ISO/IEC 9995-8. 1071 * 1072 * @return the input string, with alpha letters converted to numeric 1073 * digits using the phone keypad letter mapping. For example, 1074 * an input of "1-800-GOOG-411" will return "1-800-4664-411". 1075 */ 1076 public static String convertKeypadLettersToDigits(String input) { 1077 if (input == null) { 1078 return input; 1079 } 1080 int len = input.length(); 1081 if (len == 0) { 1082 return input; 1083 } 1084 1085 char[] out = input.toCharArray(); 1086 1087 for (int i = 0; i < len; i++) { 1088 char c = out[i]; 1089 // If this char isn't in KEYPAD_MAP at all, just leave it alone. 1090 out[i] = (char) KEYPAD_MAP.get(c, c); 1091 } 1092 1093 return new String(out); 1094 } 1095 1096 /** 1097 * The phone keypad letter mapping (see ITU E.161 or ISO/IEC 9995-8.) 1098 * TODO: This should come from a resource. 1099 */ 1100 private static final SparseIntArray KEYPAD_MAP = new SparseIntArray(); 1101 static { 1102 KEYPAD_MAP.put('a', '2'); KEYPAD_MAP.put('b', '2'); KEYPAD_MAP.put('c', '2'); 1103 KEYPAD_MAP.put('A', '2'); KEYPAD_MAP.put('B', '2'); KEYPAD_MAP.put('C', '2'); 1104 1105 KEYPAD_MAP.put('d', '3'); KEYPAD_MAP.put('e', '3'); KEYPAD_MAP.put('f', '3'); 1106 KEYPAD_MAP.put('D', '3'); KEYPAD_MAP.put('E', '3'); KEYPAD_MAP.put('F', '3'); 1107 1108 KEYPAD_MAP.put('g', '4'); KEYPAD_MAP.put('h', '4'); KEYPAD_MAP.put('i', '4'); 1109 KEYPAD_MAP.put('G', '4'); KEYPAD_MAP.put('H', '4'); KEYPAD_MAP.put('I', '4'); 1110 1111 KEYPAD_MAP.put('j', '5'); KEYPAD_MAP.put('k', '5'); KEYPAD_MAP.put('l', '5'); 1112 KEYPAD_MAP.put('J', '5'); KEYPAD_MAP.put('K', '5'); KEYPAD_MAP.put('L', '5'); 1113 1114 KEYPAD_MAP.put('m', '6'); KEYPAD_MAP.put('n', '6'); KEYPAD_MAP.put('o', '6'); 1115 KEYPAD_MAP.put('M', '6'); KEYPAD_MAP.put('N', '6'); KEYPAD_MAP.put('O', '6'); 1116 1117 KEYPAD_MAP.put('p', '7'); KEYPAD_MAP.put('q', '7'); KEYPAD_MAP.put('r', '7'); KEYPAD_MAP.put('s', '7'); 1118 KEYPAD_MAP.put('P', '7'); KEYPAD_MAP.put('Q', '7'); KEYPAD_MAP.put('R', '7'); KEYPAD_MAP.put('S', '7'); 1119 1120 KEYPAD_MAP.put('t', '8'); KEYPAD_MAP.put('u', '8'); KEYPAD_MAP.put('v', '8'); 1121 KEYPAD_MAP.put('T', '8'); KEYPAD_MAP.put('U', '8'); KEYPAD_MAP.put('V', '8'); 1122 1123 KEYPAD_MAP.put('w', '9'); KEYPAD_MAP.put('x', '9'); KEYPAD_MAP.put('y', '9'); KEYPAD_MAP.put('z', '9'); 1124 KEYPAD_MAP.put('W', '9'); KEYPAD_MAP.put('X', '9'); KEYPAD_MAP.put('Y', '9'); KEYPAD_MAP.put('Z', '9'); 1125 } 1126} 1127