PhoneNumberUtils.java revision 5214376d2b1ce082765f766be1c7d56b50df74a7
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 * and TS 11.11 "10.3.1 EF adn (Abbreviated dialing numbers)" 509 * 510 * @param bytes the data buffer 511 * @param offset should point to the TOA (aka. TON/NPI) octet after the length byte 512 * @param length is the number of bytes including TOA byte 513 * and must be at least 2 514 * 515 * @return partial string on invalid decode 516 * 517 * FIXME(mkf) support alphanumeric address type 518 * currently implemented in SMSMessage.getAddress() 519 */ 520 public static String 521 calledPartyBCDToString (byte[] bytes, int offset, int length) { 522 boolean prependPlus = false; 523 StringBuilder ret = new StringBuilder(1 + length * 2); 524 525 if (length < 2) { 526 return ""; 527 } 528 529 if ((bytes[offset] & 0xff) == TOA_International) { 530 prependPlus = true; 531 } 532 533 internalCalledPartyBCDFragmentToString( 534 ret, bytes, offset + 1, length - 1); 535 536 if (prependPlus && ret.length() == 0) { 537 // If the only thing there is a prepended plus, return "" 538 return ""; 539 } 540 541 if (prependPlus) { 542 // This is an "international number" and should have 543 // a plus prepended to the dialing number. But there 544 // can also be Gsm MMI codes as defined in TS 22.030 6.5.2 545 // so we need to handle those also. 546 // 547 // http://web.telia.com/~u47904776/gsmkode.htm is a 548 // has a nice list of some of these GSM codes. 549 // 550 // Examples are: 551 // **21*+886988171479# 552 // **21*8311234567# 553 // *21# 554 // #21# 555 // *#21# 556 // *31#+11234567890 557 // #31#+18311234567 558 // #31#8311234567 559 // 18311234567 560 // +18311234567# 561 // +18311234567 562 // Odd ball cases that some phones handled 563 // where there is no dialing number so they 564 // append the "+" 565 // *21#+ 566 // **21#+ 567 String retString = ret.toString(); 568 Pattern p = Pattern.compile("(^[#*])(.*)([#*])(.*)(#)$"); 569 Matcher m = p.matcher(retString); 570 if (m.matches()) { 571 if ("".equals(m.group(2))) { 572 // Started with two [#*] ends with # 573 // So no dialing number and we'll just 574 // append a +, this handles **21#+ 575 ret = new StringBuilder(); 576 ret.append(m.group(1)); 577 ret.append(m.group(3)); 578 ret.append(m.group(4)); 579 ret.append(m.group(5)); 580 ret.append("+"); 581 } else { 582 // Starts with [#*] and ends with # 583 // Assume group 4 is a dialing number 584 // such as *21*+1234554# 585 ret = new StringBuilder(); 586 ret.append(m.group(1)); 587 ret.append(m.group(2)); 588 ret.append(m.group(3)); 589 ret.append("+"); 590 ret.append(m.group(4)); 591 ret.append(m.group(5)); 592 } 593 } else { 594 p = Pattern.compile("(^[#*])(.*)([#*])(.*)"); 595 m = p.matcher(retString); 596 if (m.matches()) { 597 // Starts with [#*] and only one other [#*] 598 // Assume the data after last [#*] is dialing 599 // number (i.e. group 4) such as *31#+11234567890. 600 // This also includes the odd ball *21#+ 601 ret = new StringBuilder(); 602 ret.append(m.group(1)); 603 ret.append(m.group(2)); 604 ret.append(m.group(3)); 605 ret.append("+"); 606 ret.append(m.group(4)); 607 } else { 608 // Does NOT start with [#*] just prepend '+' 609 ret = new StringBuilder(); 610 ret.append('+'); 611 ret.append(retString); 612 } 613 } 614 } 615 616 return ret.toString(); 617 } 618 619 private static void 620 internalCalledPartyBCDFragmentToString( 621 StringBuilder sb, byte [] bytes, int offset, int length) { 622 for (int i = offset ; i < length + offset ; i++) { 623 byte b; 624 char c; 625 626 c = bcdToChar((byte)(bytes[i] & 0xf)); 627 628 if (c == 0) { 629 return; 630 } 631 sb.append(c); 632 633 // FIXME(mkf) TS 23.040 9.1.2.3 says 634 // "if a mobile receives 1111 in a position prior to 635 // the last semi-octet then processing shall commense with 636 // the next semi-octet and the intervening 637 // semi-octet shall be ignored" 638 // How does this jive with 24,008 10.5.4.7 639 640 b = (byte)((bytes[i] >> 4) & 0xf); 641 642 if (b == 0xf && i + 1 == length + offset) { 643 //ignore final 0xf 644 break; 645 } 646 647 c = bcdToChar(b); 648 if (c == 0) { 649 return; 650 } 651 652 sb.append(c); 653 } 654 655 } 656 657 /** 658 * Like calledPartyBCDToString, but field does not start with a 659 * TOA byte. For example: SIM ADN extension fields 660 */ 661 662 public static String 663 calledPartyBCDFragmentToString(byte [] bytes, int offset, int length) { 664 StringBuilder ret = new StringBuilder(length * 2); 665 666 internalCalledPartyBCDFragmentToString(ret, bytes, offset, length); 667 668 return ret.toString(); 669 } 670 671 /** returns 0 on invalid value */ 672 private static char 673 bcdToChar(byte b) { 674 if (b < 0xa) { 675 return (char)('0' + b); 676 } else switch (b) { 677 case 0xa: return '*'; 678 case 0xb: return '#'; 679 case 0xc: return PAUSE; 680 case 0xd: return WILD; 681 682 default: return 0; 683 } 684 } 685 686 private static int 687 charToBCD(char c) { 688 if (c >= '0' && c <= '9') { 689 return c - '0'; 690 } else if (c == '*') { 691 return 0xa; 692 } else if (c == '#') { 693 return 0xb; 694 } else if (c == PAUSE) { 695 return 0xc; 696 } else if (c == WILD) { 697 return 0xd; 698 } else { 699 throw new RuntimeException ("invalid char for BCD " + c); 700 } 701 } 702 703 /** 704 * Note: calls extractNetworkPortion(), so do not use for 705 * SIM EF[ADN] style records 706 * 707 * Exceptions thrown if extractNetworkPortion(s).length() == 0 708 */ 709 public static byte[] 710 networkPortionToCalledPartyBCD(String s) { 711 return numberToCalledPartyBCD(extractNetworkPortion(s)); 712 } 713 714 /** 715 * Return true iff the network portion of <code>address</code> is, 716 * as far as we can tell on the device, suitable for use as an SMS 717 * destination address. 718 */ 719 public static boolean isWellFormedSmsAddress(String address) { 720 String networkPortion = 721 PhoneNumberUtils.extractNetworkPortion(address); 722 723 return (!(networkPortion.equals("+") 724 || TextUtils.isEmpty(networkPortion))) 725 && isDialable(networkPortion); 726 } 727 728 public static boolean isGlobalPhoneNumber(String phoneNumber) { 729 if (TextUtils.isEmpty(phoneNumber)) { 730 return false; 731 } 732 733 Matcher match = GLOBAL_PHONE_NUMBER_PATTERN.matcher(phoneNumber); 734 return match.matches(); 735 } 736 737 private static boolean isDialable(String address) { 738 for (int i = 0, count = address.length(); i < count; i++) { 739 if (!isDialable(address.charAt(i))) { 740 return false; 741 } 742 } 743 return true; 744 } 745 746 /** 747 * Same as {@link #networkPortionToCalledPartyBCD}, but includes a 748 * one-byte length prefix. 749 */ 750 public static byte[] 751 networkPortionToCalledPartyBCDWithLength(String s) { 752 return numberToCalledPartyBCDWithLength(extractNetworkPortion(s)); 753 } 754 755 /** 756 * Convert a dialing number to BCD byte array 757 * 758 * @param number dialing number string 759 * if the dialing number starts with '+', set to internationl TOA 760 * @return BCD byte array 761 */ 762 public static byte[] 763 numberToCalledPartyBCD(String number) { 764 // The extra byte required for '+' is taken into consideration while calculating 765 // length of ret. 766 int size = (hasPlus(number) ? number.length() - 1 : number.length()); 767 byte[] ret = new byte[(size + 1) / 2 + 1]; 768 769 return numberToCalledPartyBCDHelper(ret, 0, number); 770 } 771 772 /** 773 * Same as {@link #numberToCalledPartyBCD}, but includes a 774 * one-byte length prefix. 775 */ 776 private static byte[] 777 numberToCalledPartyBCDWithLength(String number) { 778 // The extra byte required for '+' is taken into consideration while calculating 779 // length of ret. 780 int size = (hasPlus(number) ? number.length() - 1 : number.length()); 781 int length = (size + 1) / 2 + 1; 782 byte[] ret = new byte[length + 1]; 783 784 ret[0] = (byte) (length & 0xff); 785 return numberToCalledPartyBCDHelper(ret, 1, number); 786 } 787 788 private static boolean 789 hasPlus(String s) { 790 return s.indexOf('+') >= 0; 791 } 792 793 private static byte[] 794 numberToCalledPartyBCDHelper(byte[] ret, int offset, String number) { 795 if (hasPlus(number)) { 796 number = number.replaceAll("\\+", ""); 797 ret[offset] = (byte) TOA_International; 798 } else { 799 ret[offset] = (byte) TOA_Unknown; 800 } 801 802 int size = number.length(); 803 int curChar = 0; 804 int countFullBytes = ret.length - offset - 1 - ((size - curChar) & 1); 805 for (int i = 1; i < 1 + countFullBytes; i++) { 806 ret[offset + i] 807 = (byte) ((charToBCD(number.charAt(curChar++))) 808 | (charToBCD(number.charAt(curChar++))) << 4); 809 } 810 811 // The left-over octet for odd-length phone numbers should be 812 // filled with 0xf. 813 if (countFullBytes + offset < ret.length - 1) { 814 ret[ret.length - 1] 815 = (byte) (charToBCD(number.charAt(curChar)) 816 | (0xf << 4)); 817 } 818 return ret; 819 } 820 821 /** all of 'a' up to len must match non-US trunk prefix ('0') */ 822 private static boolean 823 matchTrunkPrefix(String a, int len) { 824 boolean found; 825 826 found = false; 827 828 for (int i = 0 ; i < len ; i++) { 829 char c = a.charAt(i); 830 831 if (c == '0' && !found) { 832 found = true; 833 } else if (isNonSeparator(c)) { 834 return false; 835 } 836 } 837 838 return found; 839 } 840 841 /** all of 'a' up to len must be a (+|00|011)country code) 842 * We're fast and loose with the country code. Any \d{1,3} matches */ 843 private static boolean 844 matchIntlPrefixAndCC(String a, int len) { 845 /* [^0-9*#+pwn]*(\+|0(0|11)\d\d?\d? [^0-9*#+pwn] $ */ 846 /* 0 1 2 3 45 6 7 8 */ 847 848 int state = 0; 849 for (int i = 0 ; i < len ; i++ ) { 850 char c = a.charAt(i); 851 852 switch (state) { 853 case 0: 854 if (c == '+') state = 1; 855 else if (c == '0') state = 2; 856 else if (isNonSeparator(c)) return false; 857 break; 858 859 case 2: 860 if (c == '0') state = 3; 861 else if (c == '1') state = 4; 862 else if (isNonSeparator(c)) return false; 863 break; 864 865 case 4: 866 if (c == '1') state = 5; 867 else if (isNonSeparator(c)) return false; 868 break; 869 870 case 1: 871 case 3: 872 case 5: 873 if (isISODigit(c)) state = 6; 874 else if (isNonSeparator(c)) return false; 875 break; 876 877 case 6: 878 case 7: 879 if (isISODigit(c)) state++; 880 else if (isNonSeparator(c)) return false; 881 break; 882 883 default: 884 if (isNonSeparator(c)) return false; 885 } 886 } 887 888 return state == 6 || state == 7 || state == 8; 889 } 890 891 //================ Number formatting ========================= 892 893 /** The current locale is unknown, look for a country code or don't format */ 894 public static final int FORMAT_UNKNOWN = 0; 895 /** NANP formatting */ 896 public static final int FORMAT_NANP = 1; 897 /** Japanese formatting */ 898 public static final int FORMAT_JAPAN = 2; 899 900 /** List of country codes for countries that use the NANP */ 901 private static final String[] NANP_COUNTRIES = new String[] { 902 "US", // United States 903 "CA", // Canada 904 "AS", // American Samoa 905 "AI", // Anguilla 906 "AG", // Antigua and Barbuda 907 "BS", // Bahamas 908 "BB", // Barbados 909 "BM", // Bermuda 910 "VG", // British Virgin Islands 911 "KY", // Cayman Islands 912 "DM", // Dominica 913 "DO", // Dominican Republic 914 "GD", // Grenada 915 "GU", // Guam 916 "JM", // Jamaica 917 "PR", // Puerto Rico 918 "MS", // Montserrat 919 "NP", // Northern Mariana Islands 920 "KN", // Saint Kitts and Nevis 921 "LC", // Saint Lucia 922 "VC", // Saint Vincent and the Grenadines 923 "TT", // Trinidad and Tobago 924 "TC", // Turks and Caicos Islands 925 "VI", // U.S. Virgin Islands 926 }; 927 928 /** 929 * Breaks the given number down and formats it according to the rules 930 * for the country the number is from. 931 * 932 * @param source the phone number to format 933 * @return a locally acceptable formatting of the input, or the raw input if 934 * formatting rules aren't known for the number 935 */ 936 public static String formatNumber(String source) { 937 SpannableStringBuilder text = new SpannableStringBuilder(source); 938 formatNumber(text, getFormatTypeForLocale(Locale.getDefault())); 939 return text.toString(); 940 } 941 942 /** 943 * Returns the phone number formatting type for the given locale. 944 * 945 * @param locale The locale of interest, usually {@link Locale#getDefault()} 946 * @return the formatting type for the given locale, or FORMAT_UNKNOWN if the formatting 947 * rules are not known for the given locale 948 */ 949 public static int getFormatTypeForLocale(Locale locale) { 950 String country = locale.getCountry(); 951 952 // Check for the NANP countries 953 int length = NANP_COUNTRIES.length; 954 for (int i = 0; i < length; i++) { 955 if (NANP_COUNTRIES[i].equals(country)) { 956 return FORMAT_NANP; 957 } 958 } 959 if (locale.equals(Locale.JAPAN)) { 960 return FORMAT_JAPAN; 961 } 962 return FORMAT_UNKNOWN; 963 } 964 965 /** 966 * Formats a phone number in-place. Currently only supports NANP formatting. 967 * 968 * @param text The number to be formatted, will be modified with the formatting 969 * @param defaultFormattingType The default formatting rules to apply if the number does 970 * not begin with +<country_code> 971 */ 972 public static void formatNumber(Editable text, int defaultFormattingType) { 973 int formatType = defaultFormattingType; 974 975 if (text.length() > 2 && text.charAt(0) == '+') { 976 if (text.charAt(1) == '1') { 977 formatType = FORMAT_NANP; 978 } else if (text.length() >= 3 && text.charAt(1) == '8' 979 && text.charAt(2) == '1') { 980 formatType = FORMAT_JAPAN; 981 } else { 982 return; 983 } 984 } 985 986 switch (formatType) { 987 case FORMAT_NANP: 988 formatNanpNumber(text); 989 return; 990 case FORMAT_JAPAN: 991 formatJapaneseNumber(text); 992 return; 993 } 994 } 995 996 private static final int NANP_STATE_DIGIT = 1; 997 private static final int NANP_STATE_PLUS = 2; 998 private static final int NANP_STATE_ONE = 3; 999 private static final int NANP_STATE_DASH = 4; 1000 1001 /** 1002 * Formats a phone number in-place using the NANP formatting rules. Numbers will be formatted 1003 * as: 1004 * 1005 * <p><code> 1006 * xxxxx 1007 * xxx-xxxx 1008 * xxx-xxx-xxxx 1009 * 1-xxx-xxx-xxxx 1010 * +1-xxx-xxx-xxxx 1011 * </code></p> 1012 * 1013 * @param text the number to be formatted, will be modified with the formatting 1014 */ 1015 public static void formatNanpNumber(Editable text) { 1016 int length = text.length(); 1017 if (length > "+1-nnn-nnn-nnnn".length()) { 1018 // The string is too long to be formatted 1019 return; 1020 } else if (length <= 5) { 1021 // The string is either a shortcode or too short to be formatted 1022 return; 1023 } 1024 1025 CharSequence saved = text.subSequence(0, length); 1026 1027 // Strip the dashes first, as we're going to add them back 1028 int p = 0; 1029 while (p < text.length()) { 1030 if (text.charAt(p) == '-') { 1031 text.delete(p, p + 1); 1032 } else { 1033 p++; 1034 } 1035 } 1036 length = text.length(); 1037 1038 // When scanning the number we record where dashes need to be added, 1039 // if they're non-0 at the end of the scan the dashes will be added in 1040 // the proper places. 1041 int dashPositions[] = new int[3]; 1042 int numDashes = 0; 1043 1044 int state = NANP_STATE_DIGIT; 1045 int numDigits = 0; 1046 for (int i = 0; i < length; i++) { 1047 char c = text.charAt(i); 1048 switch (c) { 1049 case '1': 1050 if (numDigits == 0 || state == NANP_STATE_PLUS) { 1051 state = NANP_STATE_ONE; 1052 break; 1053 } 1054 // fall through 1055 case '2': 1056 case '3': 1057 case '4': 1058 case '5': 1059 case '6': 1060 case '7': 1061 case '8': 1062 case '9': 1063 case '0': 1064 if (state == NANP_STATE_PLUS) { 1065 // Only NANP number supported for now 1066 text.replace(0, length, saved); 1067 return; 1068 } else if (state == NANP_STATE_ONE) { 1069 // Found either +1 or 1, follow it up with a dash 1070 dashPositions[numDashes++] = i; 1071 } else if (state != NANP_STATE_DASH && (numDigits == 3 || numDigits == 6)) { 1072 // Found a digit that should be after a dash that isn't 1073 dashPositions[numDashes++] = i; 1074 } 1075 state = NANP_STATE_DIGIT; 1076 numDigits++; 1077 break; 1078 1079 case '-': 1080 state = NANP_STATE_DASH; 1081 break; 1082 1083 case '+': 1084 if (i == 0) { 1085 // Plus is only allowed as the first character 1086 state = NANP_STATE_PLUS; 1087 break; 1088 } 1089 // Fall through 1090 default: 1091 // Unknown character, bail on formatting 1092 text.replace(0, length, saved); 1093 return; 1094 } 1095 } 1096 1097 if (numDigits == 7) { 1098 // With 7 digits we want xxx-xxxx, not xxx-xxx-x 1099 numDashes--; 1100 } 1101 1102 // Actually put the dashes in place 1103 for (int i = 0; i < numDashes; i++) { 1104 int pos = dashPositions[i]; 1105 text.replace(pos + i, pos + i, "-"); 1106 } 1107 1108 // Remove trailing dashes 1109 int len = text.length(); 1110 while (len > 0) { 1111 if (text.charAt(len - 1) == '-') { 1112 text.delete(len - 1, len); 1113 len--; 1114 } else { 1115 break; 1116 } 1117 } 1118 } 1119 1120 /** 1121 * Formats a phone number in-place using the Japanese formatting rules. 1122 * Numbers will be formatted as: 1123 * 1124 * <p><code> 1125 * 03-xxxx-xxxx 1126 * 090-xxxx-xxxx 1127 * 0120-xxx-xxx 1128 * +81-3-xxxx-xxxx 1129 * +81-90-xxxx-xxxx 1130 * </code></p> 1131 * 1132 * @param text the number to be formatted, will be modified with 1133 * the formatting 1134 */ 1135 public static void formatJapaneseNumber(Editable text) { 1136 JapanesePhoneNumberFormatter.format(text); 1137 } 1138 1139 // Three and four digit phone numbers for either special services 1140 // or from the network (eg carrier-originated SMS messages) should 1141 // not match 1142 static final int MIN_MATCH = 5; 1143 1144 /** 1145 * isEmergencyNumber: checks a given number against the list of 1146 * emergency numbers provided by the RIL and SIM card. 1147 * 1148 * @param number the number to look up. 1149 * @return if the number is in the list of emergency numbers 1150 * listed in the ril / sim, then return true, otherwise false. 1151 */ 1152 public static boolean isEmergencyNumber(String number) { 1153 // Strip the separators from the number before comparing it 1154 // to the list. 1155 number = extractNetworkPortion(number); 1156 1157 // retrieve the list of emergency numbers 1158 String numbers = SystemProperties.get("ro.ril.ecclist"); 1159 1160 if (!TextUtils.isEmpty(numbers)) { 1161 // searches through the comma-separated list for a match, 1162 // return true if one is found. 1163 for (String emergencyNum : numbers.split(",")) { 1164 if (emergencyNum.equals(number)) { 1165 return true; 1166 } 1167 } 1168 // no matches found against the list! 1169 return false; 1170 } 1171 1172 //no ecclist system property, so use our own list. 1173 return (number.equals("112") || number.equals("911")); 1174 } 1175 1176 /** 1177 * Translates any alphabetic letters (i.e. [A-Za-z]) in the 1178 * specified phone number into the equivalent numeric digits, 1179 * according to the phone keypad letter mapping described in 1180 * ITU E.161 and ISO/IEC 9995-8. 1181 * 1182 * @return the input string, with alpha letters converted to numeric 1183 * digits using the phone keypad letter mapping. For example, 1184 * an input of "1-800-GOOG-411" will return "1-800-4664-411". 1185 */ 1186 public static String convertKeypadLettersToDigits(String input) { 1187 if (input == null) { 1188 return input; 1189 } 1190 int len = input.length(); 1191 if (len == 0) { 1192 return input; 1193 } 1194 1195 char[] out = input.toCharArray(); 1196 1197 for (int i = 0; i < len; i++) { 1198 char c = out[i]; 1199 // If this char isn't in KEYPAD_MAP at all, just leave it alone. 1200 out[i] = (char) KEYPAD_MAP.get(c, c); 1201 } 1202 1203 return new String(out); 1204 } 1205 1206 /** 1207 * The phone keypad letter mapping (see ITU E.161 or ISO/IEC 9995-8.) 1208 * TODO: This should come from a resource. 1209 */ 1210 private static final SparseIntArray KEYPAD_MAP = new SparseIntArray(); 1211 static { 1212 KEYPAD_MAP.put('a', '2'); KEYPAD_MAP.put('b', '2'); KEYPAD_MAP.put('c', '2'); 1213 KEYPAD_MAP.put('A', '2'); KEYPAD_MAP.put('B', '2'); KEYPAD_MAP.put('C', '2'); 1214 1215 KEYPAD_MAP.put('d', '3'); KEYPAD_MAP.put('e', '3'); KEYPAD_MAP.put('f', '3'); 1216 KEYPAD_MAP.put('D', '3'); KEYPAD_MAP.put('E', '3'); KEYPAD_MAP.put('F', '3'); 1217 1218 KEYPAD_MAP.put('g', '4'); KEYPAD_MAP.put('h', '4'); KEYPAD_MAP.put('i', '4'); 1219 KEYPAD_MAP.put('G', '4'); KEYPAD_MAP.put('H', '4'); KEYPAD_MAP.put('I', '4'); 1220 1221 KEYPAD_MAP.put('j', '5'); KEYPAD_MAP.put('k', '5'); KEYPAD_MAP.put('l', '5'); 1222 KEYPAD_MAP.put('J', '5'); KEYPAD_MAP.put('K', '5'); KEYPAD_MAP.put('L', '5'); 1223 1224 KEYPAD_MAP.put('m', '6'); KEYPAD_MAP.put('n', '6'); KEYPAD_MAP.put('o', '6'); 1225 KEYPAD_MAP.put('M', '6'); KEYPAD_MAP.put('N', '6'); KEYPAD_MAP.put('O', '6'); 1226 1227 KEYPAD_MAP.put('p', '7'); KEYPAD_MAP.put('q', '7'); KEYPAD_MAP.put('r', '7'); KEYPAD_MAP.put('s', '7'); 1228 KEYPAD_MAP.put('P', '7'); KEYPAD_MAP.put('Q', '7'); KEYPAD_MAP.put('R', '7'); KEYPAD_MAP.put('S', '7'); 1229 1230 KEYPAD_MAP.put('t', '8'); KEYPAD_MAP.put('u', '8'); KEYPAD_MAP.put('v', '8'); 1231 KEYPAD_MAP.put('T', '8'); KEYPAD_MAP.put('U', '8'); KEYPAD_MAP.put('V', '8'); 1232 1233 KEYPAD_MAP.put('w', '9'); KEYPAD_MAP.put('x', '9'); KEYPAD_MAP.put('y', '9'); KEYPAD_MAP.put('z', '9'); 1234 KEYPAD_MAP.put('W', '9'); KEYPAD_MAP.put('X', '9'); KEYPAD_MAP.put('Y', '9'); KEYPAD_MAP.put('Z', '9'); 1235 } 1236} 1237