PhoneNumberUtils.java revision 694bd8bf733aee372705e00d13964a20e5b16209
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.provider.ContactsContract; 26import android.text.Editable; 27import android.text.SpannableStringBuilder; 28import android.text.TextUtils; 29import android.util.Log; 30import android.util.SparseIntArray; 31 32import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_ISO_COUNTRY; 33import static com.android.internal.telephony.TelephonyProperties.PROPERTY_IDP_STRING; 34import static com.android.internal.telephony.TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY; 35 36import java.util.Locale; 37import java.util.regex.Matcher; 38import java.util.regex.Pattern; 39 40/** 41 * Various utilities for dealing with phone number strings. 42 */ 43public class PhoneNumberUtils 44{ 45 /* 46 * Special characters 47 * 48 * (See "What is a phone number?" doc) 49 * 'p' --- GSM pause character, same as comma 50 * 'n' --- GSM wild character 51 * 'w' --- GSM wait character 52 */ 53 public static final char PAUSE = ','; 54 public static final char WAIT = ';'; 55 public static final char WILD = 'N'; 56 57 /* 58 * TOA = TON + NPI 59 * See TS 24.008 section 10.5.4.7 for details. 60 * These are the only really useful TOA values 61 */ 62 public static final int TOA_International = 0x91; 63 public static final int TOA_Unknown = 0x81; 64 65 static final String LOG_TAG = "PhoneNumberUtils"; 66 private static final boolean DBG = false; 67 68 /* 69 * global-phone-number = ["+"] 1*( DIGIT / written-sep ) 70 * written-sep = ("-"/".") 71 */ 72 private static final Pattern GLOBAL_PHONE_NUMBER_PATTERN = 73 Pattern.compile("[\\+]?[0-9.-]+"); 74 75 /** True if c is ISO-LATIN characters 0-9 */ 76 public static boolean 77 isISODigit (char c) { 78 return c >= '0' && c <= '9'; 79 } 80 81 /** True if c is ISO-LATIN characters 0-9, *, # */ 82 public final static boolean 83 is12Key(char c) { 84 return (c >= '0' && c <= '9') || c == '*' || c == '#'; 85 } 86 87 /** True if c is ISO-LATIN characters 0-9, *, # , +, WILD */ 88 public final static boolean 89 isDialable(char c) { 90 return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+' || c == WILD; 91 } 92 93 /** True if c is ISO-LATIN characters 0-9, *, # , + (no WILD) */ 94 public final static boolean 95 isReallyDialable(char c) { 96 return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+'; 97 } 98 99 /** True if c is ISO-LATIN characters 0-9, *, # , +, WILD, WAIT, PAUSE */ 100 public final static boolean 101 isNonSeparator(char c) { 102 return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+' 103 || c == WILD || c == WAIT || c == PAUSE; 104 } 105 106 /** This any anything to the right of this char is part of the 107 * post-dial string (eg this is PAUSE or WAIT) 108 */ 109 public final static boolean 110 isStartsPostDial (char c) { 111 return c == PAUSE || c == WAIT; 112 } 113 114 /** Returns true if ch is not dialable or alpha char */ 115 private static boolean isSeparator(char ch) { 116 return !isDialable(ch) && !(('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z')); 117 } 118 119 /** Extracts the phone number from an Intent. 120 * 121 * @param intent the intent to get the number of 122 * @param context a context to use for database access 123 * 124 * @return the phone number that would be called by the intent, or 125 * <code>null</code> if the number cannot be found. 126 */ 127 public static String getNumberFromIntent(Intent intent, Context context) { 128 String number = null; 129 130 Uri uri = intent.getData(); 131 String scheme = uri.getScheme(); 132 133 if (scheme.equals("tel")) { 134 return uri.getSchemeSpecificPart(); 135 } 136 137 // TODO: We don't check for SecurityException here (requires 138 // READ_PHONE_STATE permission). 139 if (scheme.equals("voicemail")) { 140 return TelephonyManager.getDefault().getVoiceMailNumber(); 141 } 142 143 if (context == null) { 144 return null; 145 } 146 147 String type = intent.resolveType(context); 148 String phoneColumn = null; 149 150 // Correctly read out the phone entry based on requested provider 151 final String authority = uri.getAuthority(); 152 if (Contacts.AUTHORITY.equals(authority)) { 153 phoneColumn = Contacts.People.Phones.NUMBER; 154 } else if (ContactsContract.AUTHORITY.equals(authority)) { 155 phoneColumn = ContactsContract.CommonDataKinds.Phone.NUMBER; 156 } 157 158 final Cursor c = context.getContentResolver().query(uri, new String[] { 159 phoneColumn 160 }, null, null, null); 161 if (c != null) { 162 try { 163 if (c.moveToFirst()) { 164 number = c.getString(c.getColumnIndex(phoneColumn)); 165 } 166 } finally { 167 c.close(); 168 } 169 } 170 171 return number; 172 } 173 174 /** Extracts the network address portion and canonicalizes 175 * (filters out separators.) 176 * Network address portion is everything up to DTMF control digit 177 * separators (pause or wait), but without non-dialable characters. 178 * 179 * Please note that the GSM wild character is allowed in the result. 180 * This must be resolved before dialing. 181 * 182 * Allows + only in the first position in the result string. 183 * 184 * Returns null if phoneNumber == null 185 */ 186 public static String 187 extractNetworkPortion(String phoneNumber) { 188 if (phoneNumber == null) { 189 return null; 190 } 191 192 int len = phoneNumber.length(); 193 StringBuilder ret = new StringBuilder(len); 194 boolean firstCharAdded = false; 195 196 for (int i = 0; i < len; i++) { 197 char c = phoneNumber.charAt(i); 198 if (isDialable(c) && (c != '+' || !firstCharAdded)) { 199 firstCharAdded = true; 200 ret.append(c); 201 } else if (isStartsPostDial (c)) { 202 break; 203 } 204 } 205 206 return ret.toString(); 207 } 208 209 /** 210 * Strips separators from a phone number string. 211 * @param phoneNumber phone number to strip. 212 * @return phone string stripped of separators. 213 */ 214 public static String stripSeparators(String phoneNumber) { 215 if (phoneNumber == null) { 216 return null; 217 } 218 int len = phoneNumber.length(); 219 StringBuilder ret = new StringBuilder(len); 220 221 for (int i = 0; i < len; i++) { 222 char c = phoneNumber.charAt(i); 223 if (isNonSeparator(c)) { 224 ret.append(c); 225 } 226 } 227 228 return ret.toString(); 229 } 230 231 /** or -1 if both are negative */ 232 static private int 233 minPositive (int a, int b) { 234 if (a >= 0 && b >= 0) { 235 return (a < b) ? a : b; 236 } else if (a >= 0) { /* && b < 0 */ 237 return a; 238 } else if (b >= 0) { /* && a < 0 */ 239 return b; 240 } else { /* a < 0 && b < 0 */ 241 return -1; 242 } 243 } 244 245 private static void log(String msg) { 246 Log.d(LOG_TAG, msg); 247 } 248 /** index of the last character of the network portion 249 * (eg anything after is a post-dial string) 250 */ 251 static private int 252 indexOfLastNetworkChar(String a) { 253 int pIndex, wIndex; 254 int origLength; 255 int trimIndex; 256 257 origLength = a.length(); 258 259 pIndex = a.indexOf(PAUSE); 260 wIndex = a.indexOf(WAIT); 261 262 trimIndex = minPositive(pIndex, wIndex); 263 264 if (trimIndex < 0) { 265 return origLength - 1; 266 } else { 267 return trimIndex - 1; 268 } 269 } 270 271 /** 272 * Extracts the post-dial sequence of DTMF control digits, pauses, and 273 * waits. Strips separators. This string may be empty, but will not be null 274 * unless phoneNumber == null. 275 * 276 * Returns null if phoneNumber == null 277 */ 278 279 public static String 280 extractPostDialPortion(String phoneNumber) { 281 if (phoneNumber == null) return null; 282 283 int trimIndex; 284 StringBuilder ret = new StringBuilder(); 285 286 trimIndex = indexOfLastNetworkChar (phoneNumber); 287 288 for (int i = trimIndex + 1, s = phoneNumber.length() 289 ; i < s; i++ 290 ) { 291 char c = phoneNumber.charAt(i); 292 if (isNonSeparator(c)) { 293 ret.append(c); 294 } 295 } 296 297 return ret.toString(); 298 } 299 300 /** 301 * Compare phone numbers a and b, return true if they're identical enough for caller ID purposes. 302 */ 303 public static boolean compare(String a, String b) { 304 // We've used loose comparation at least Eclair, which may change in the future. 305 return compare(a, b, false); 306 } 307 308 /** 309 * @hide only for testing. 310 */ 311 public static boolean compare(String a, String b, boolean useStrictComparation) { 312 return (useStrictComparation ? compareStrictly(a, b) : compareLoosely(a, b)); 313 } 314 315 /** 316 * Compare phone numbers a and b, return true if they're identical 317 * enough for caller ID purposes. 318 * 319 * - Compares from right to left 320 * - requires MIN_MATCH (7) characters to match 321 * - handles common trunk prefixes and international prefixes 322 * (basically, everything except the Russian trunk prefix) 323 * 324 * Note that this method does not return false even when the two phone numbers 325 * are not exactly same; rather; we can call this method "similar()", not "equals()". 326 * 327 * @hide 328 */ 329 public static boolean 330 compareLoosely(String a, String b) { 331 int ia, ib; 332 int matched; 333 334 if (a == null || b == null) return a == b; 335 336 if (a.length() == 0 || b.length() == 0) { 337 return false; 338 } 339 340 ia = indexOfLastNetworkChar (a); 341 ib = indexOfLastNetworkChar (b); 342 matched = 0; 343 344 while (ia >= 0 && ib >=0) { 345 char ca, cb; 346 boolean skipCmp = false; 347 348 ca = a.charAt(ia); 349 350 if (!isDialable(ca)) { 351 ia--; 352 skipCmp = true; 353 } 354 355 cb = b.charAt(ib); 356 357 if (!isDialable(cb)) { 358 ib--; 359 skipCmp = true; 360 } 361 362 if (!skipCmp) { 363 if (cb != ca && ca != WILD && cb != WILD) { 364 break; 365 } 366 ia--; ib--; matched++; 367 } 368 } 369 370 if (matched < MIN_MATCH) { 371 int aLen = a.length(); 372 373 // if the input strings match, but their lengths < MIN_MATCH, 374 // treat them as equal. 375 if (aLen == b.length() && aLen == matched) { 376 return true; 377 } 378 return false; 379 } 380 381 // At least one string has matched completely; 382 if (matched >= MIN_MATCH && (ia < 0 || ib < 0)) { 383 return true; 384 } 385 386 /* 387 * Now, what remains must be one of the following for a 388 * match: 389 * 390 * - a '+' on one and a '00' or a '011' on the other 391 * - a '0' on one and a (+,00)<country code> on the other 392 * (for this, a '0' and a '00' prefix would have succeeded above) 393 */ 394 395 if (matchIntlPrefix(a, ia + 1) 396 && matchIntlPrefix (b, ib +1) 397 ) { 398 return true; 399 } 400 401 if (matchTrunkPrefix(a, ia + 1) 402 && matchIntlPrefixAndCC(b, ib +1) 403 ) { 404 return true; 405 } 406 407 if (matchTrunkPrefix(b, ib + 1) 408 && matchIntlPrefixAndCC(a, ia +1) 409 ) { 410 return true; 411 } 412 413 return false; 414 } 415 416 /** 417 * @hide 418 */ 419 public static boolean 420 compareStrictly(String a, String b) { 421 return compareStrictly(a, b, true); 422 } 423 424 /** 425 * @hide 426 */ 427 public static boolean 428 compareStrictly(String a, String b, boolean acceptInvalidCCCPrefix) { 429 if (a == null || b == null) { 430 return a == b; 431 } else if (a.length() == 0 && b.length() == 0) { 432 return false; 433 } 434 435 int forwardIndexA = 0; 436 int forwardIndexB = 0; 437 438 CountryCallingCodeAndNewIndex cccA = 439 tryGetCountryCallingCodeAndNewIndex(a, acceptInvalidCCCPrefix); 440 CountryCallingCodeAndNewIndex cccB = 441 tryGetCountryCallingCodeAndNewIndex(b, acceptInvalidCCCPrefix); 442 boolean bothHasCountryCallingCode = false; 443 boolean okToIgnorePrefix = true; 444 boolean trunkPrefixIsOmittedA = false; 445 boolean trunkPrefixIsOmittedB = false; 446 if (cccA != null && cccB != null) { 447 if (cccA.countryCallingCode != cccB.countryCallingCode) { 448 // Different Country Calling Code. Must be different phone number. 449 return false; 450 } 451 // When both have ccc, do not ignore trunk prefix. Without this, 452 // "+81123123" becomes same as "+810123123" (+81 == Japan) 453 okToIgnorePrefix = false; 454 bothHasCountryCallingCode = true; 455 forwardIndexA = cccA.newIndex; 456 forwardIndexB = cccB.newIndex; 457 } else if (cccA == null && cccB == null) { 458 // When both do not have ccc, do not ignore trunk prefix. Without this, 459 // "123123" becomes same as "0123123" 460 okToIgnorePrefix = false; 461 } else { 462 if (cccA != null) { 463 forwardIndexA = cccA.newIndex; 464 } else { 465 int tmp = tryGetTrunkPrefixOmittedIndex(b, 0); 466 if (tmp >= 0) { 467 forwardIndexA = tmp; 468 trunkPrefixIsOmittedA = true; 469 } 470 } 471 if (cccB != null) { 472 forwardIndexB = cccB.newIndex; 473 } else { 474 int tmp = tryGetTrunkPrefixOmittedIndex(b, 0); 475 if (tmp >= 0) { 476 forwardIndexB = tmp; 477 trunkPrefixIsOmittedB = true; 478 } 479 } 480 } 481 482 int backwardIndexA = a.length() - 1; 483 int backwardIndexB = b.length() - 1; 484 while (backwardIndexA >= forwardIndexA && backwardIndexB >= forwardIndexB) { 485 boolean skip_compare = false; 486 final char chA = a.charAt(backwardIndexA); 487 final char chB = b.charAt(backwardIndexB); 488 if (isSeparator(chA)) { 489 backwardIndexA--; 490 skip_compare = true; 491 } 492 if (isSeparator(chB)) { 493 backwardIndexB--; 494 skip_compare = true; 495 } 496 497 if (!skip_compare) { 498 if (chA != chB) { 499 return false; 500 } 501 backwardIndexA--; 502 backwardIndexB--; 503 } 504 } 505 506 if (okToIgnorePrefix) { 507 if ((trunkPrefixIsOmittedA && forwardIndexA <= backwardIndexA) || 508 !checkPrefixIsIgnorable(a, forwardIndexA, backwardIndexA)) { 509 if (acceptInvalidCCCPrefix) { 510 // Maybe the code handling the special case for Thailand makes the 511 // result garbled, so disable the code and try again. 512 // e.g. "16610001234" must equal to "6610001234", but with 513 // Thailand-case handling code, they become equal to each other. 514 // 515 // Note: we select simplicity rather than adding some complicated 516 // logic here for performance(like "checking whether remaining 517 // numbers are just 66 or not"), assuming inputs are small 518 // enough. 519 return compare(a, b, false); 520 } else { 521 return false; 522 } 523 } 524 if ((trunkPrefixIsOmittedB && forwardIndexB <= backwardIndexB) || 525 !checkPrefixIsIgnorable(b, forwardIndexA, backwardIndexB)) { 526 if (acceptInvalidCCCPrefix) { 527 return compare(a, b, false); 528 } else { 529 return false; 530 } 531 } 532 } else { 533 // In the US, 1-650-555-1234 must be equal to 650-555-1234, 534 // while 090-1234-1234 must not be equalt to 90-1234-1234 in Japan. 535 // This request exists just in US (with 1 trunk (NDD) prefix). 536 // In addition, "011 11 7005554141" must not equal to "+17005554141", 537 // while "011 1 7005554141" must equal to "+17005554141" 538 // 539 // In this comparison, we ignore the prefix '1' just once, when 540 // - at least either does not have CCC, or 541 // - the remaining non-separator number is 1 542 boolean maybeNamp = !bothHasCountryCallingCode; 543 while (backwardIndexA >= forwardIndexA) { 544 final char chA = a.charAt(backwardIndexA); 545 if (isDialable(chA)) { 546 if (maybeNamp && tryGetISODigit(chA) == 1) { 547 maybeNamp = false; 548 } else { 549 return false; 550 } 551 } 552 backwardIndexA--; 553 } 554 while (backwardIndexB >= forwardIndexB) { 555 final char chB = b.charAt(backwardIndexB); 556 if (isDialable(chB)) { 557 if (maybeNamp && tryGetISODigit(chB) == 1) { 558 maybeNamp = false; 559 } else { 560 return false; 561 } 562 } 563 backwardIndexB--; 564 } 565 } 566 567 return true; 568 } 569 570 /** 571 * Returns the rightmost MIN_MATCH (5) characters in the network portion 572 * in *reversed* order 573 * 574 * This can be used to do a database lookup against the column 575 * that stores getStrippedReversed() 576 * 577 * Returns null if phoneNumber == null 578 */ 579 public static String 580 toCallerIDMinMatch(String phoneNumber) { 581 String np = extractNetworkPortion(phoneNumber); 582 return internalGetStrippedReversed(np, MIN_MATCH); 583 } 584 585 /** 586 * Returns the network portion reversed. 587 * This string is intended to go into an index column for a 588 * database lookup. 589 * 590 * Returns null if phoneNumber == null 591 */ 592 public static String 593 getStrippedReversed(String phoneNumber) { 594 String np = extractNetworkPortion(phoneNumber); 595 596 if (np == null) return null; 597 598 return internalGetStrippedReversed(np, np.length()); 599 } 600 601 /** 602 * Returns the last numDigits of the reversed phone number 603 * Returns null if np == null 604 */ 605 private static String 606 internalGetStrippedReversed(String np, int numDigits) { 607 if (np == null) return null; 608 609 StringBuilder ret = new StringBuilder(numDigits); 610 int length = np.length(); 611 612 for (int i = length - 1, s = length 613 ; i >= 0 && (s - i) <= numDigits ; i-- 614 ) { 615 char c = np.charAt(i); 616 617 ret.append(c); 618 } 619 620 return ret.toString(); 621 } 622 623 /** 624 * Basically: makes sure there's a + in front of a 625 * TOA_International number 626 * 627 * Returns null if s == null 628 */ 629 public static String 630 stringFromStringAndTOA(String s, int TOA) { 631 if (s == null) return null; 632 633 if (TOA == TOA_International && s.length() > 0 && s.charAt(0) != '+') { 634 return "+" + s; 635 } 636 637 return s; 638 } 639 640 /** 641 * Returns the TOA for the given dial string 642 * Basically, returns TOA_International if there's a + prefix 643 */ 644 645 public static int 646 toaFromString(String s) { 647 if (s != null && s.length() > 0 && s.charAt(0) == '+') { 648 return TOA_International; 649 } 650 651 return TOA_Unknown; 652 } 653 654 /** 655 * 3GPP TS 24.008 10.5.4.7 656 * Called Party BCD Number 657 * 658 * See Also TS 51.011 10.5.1 "dialing number/ssc string" 659 * and TS 11.11 "10.3.1 EF adn (Abbreviated dialing numbers)" 660 * 661 * @param bytes the data buffer 662 * @param offset should point to the TOA (aka. TON/NPI) octet after the length byte 663 * @param length is the number of bytes including TOA byte 664 * and must be at least 2 665 * 666 * @return partial string on invalid decode 667 * 668 * FIXME(mkf) support alphanumeric address type 669 * currently implemented in SMSMessage.getAddress() 670 */ 671 public static String 672 calledPartyBCDToString (byte[] bytes, int offset, int length) { 673 boolean prependPlus = false; 674 StringBuilder ret = new StringBuilder(1 + length * 2); 675 676 if (length < 2) { 677 return ""; 678 } 679 680 if ((bytes[offset] & 0xff) == TOA_International) { 681 prependPlus = true; 682 } 683 684 internalCalledPartyBCDFragmentToString( 685 ret, bytes, offset + 1, length - 1); 686 687 if (prependPlus && ret.length() == 0) { 688 // If the only thing there is a prepended plus, return "" 689 return ""; 690 } 691 692 if (prependPlus) { 693 // This is an "international number" and should have 694 // a plus prepended to the dialing number. But there 695 // can also be Gsm MMI codes as defined in TS 22.030 6.5.2 696 // so we need to handle those also. 697 // 698 // http://web.telia.com/~u47904776/gsmkode.htm is a 699 // has a nice list of some of these GSM codes. 700 // 701 // Examples are: 702 // **21*+886988171479# 703 // **21*8311234567# 704 // *21# 705 // #21# 706 // *#21# 707 // *31#+11234567890 708 // #31#+18311234567 709 // #31#8311234567 710 // 18311234567 711 // +18311234567# 712 // +18311234567 713 // Odd ball cases that some phones handled 714 // where there is no dialing number so they 715 // append the "+" 716 // *21#+ 717 // **21#+ 718 String retString = ret.toString(); 719 Pattern p = Pattern.compile("(^[#*])(.*)([#*])(.*)(#)$"); 720 Matcher m = p.matcher(retString); 721 if (m.matches()) { 722 if ("".equals(m.group(2))) { 723 // Started with two [#*] ends with # 724 // So no dialing number and we'll just 725 // append a +, this handles **21#+ 726 ret = new StringBuilder(); 727 ret.append(m.group(1)); 728 ret.append(m.group(3)); 729 ret.append(m.group(4)); 730 ret.append(m.group(5)); 731 ret.append("+"); 732 } else { 733 // Starts with [#*] and ends with # 734 // Assume group 4 is a dialing number 735 // such as *21*+1234554# 736 ret = new StringBuilder(); 737 ret.append(m.group(1)); 738 ret.append(m.group(2)); 739 ret.append(m.group(3)); 740 ret.append("+"); 741 ret.append(m.group(4)); 742 ret.append(m.group(5)); 743 } 744 } else { 745 p = Pattern.compile("(^[#*])(.*)([#*])(.*)"); 746 m = p.matcher(retString); 747 if (m.matches()) { 748 // Starts with [#*] and only one other [#*] 749 // Assume the data after last [#*] is dialing 750 // number (i.e. group 4) such as *31#+11234567890. 751 // This also includes the odd ball *21#+ 752 ret = new StringBuilder(); 753 ret.append(m.group(1)); 754 ret.append(m.group(2)); 755 ret.append(m.group(3)); 756 ret.append("+"); 757 ret.append(m.group(4)); 758 } else { 759 // Does NOT start with [#*] just prepend '+' 760 ret = new StringBuilder(); 761 ret.append('+'); 762 ret.append(retString); 763 } 764 } 765 } 766 767 return ret.toString(); 768 } 769 770 private static void 771 internalCalledPartyBCDFragmentToString( 772 StringBuilder sb, byte [] bytes, int offset, int length) { 773 for (int i = offset ; i < length + offset ; i++) { 774 byte b; 775 char c; 776 777 c = bcdToChar((byte)(bytes[i] & 0xf)); 778 779 if (c == 0) { 780 return; 781 } 782 sb.append(c); 783 784 // FIXME(mkf) TS 23.040 9.1.2.3 says 785 // "if a mobile receives 1111 in a position prior to 786 // the last semi-octet then processing shall commense with 787 // the next semi-octet and the intervening 788 // semi-octet shall be ignored" 789 // How does this jive with 24,008 10.5.4.7 790 791 b = (byte)((bytes[i] >> 4) & 0xf); 792 793 if (b == 0xf && i + 1 == length + offset) { 794 //ignore final 0xf 795 break; 796 } 797 798 c = bcdToChar(b); 799 if (c == 0) { 800 return; 801 } 802 803 sb.append(c); 804 } 805 806 } 807 808 /** 809 * Like calledPartyBCDToString, but field does not start with a 810 * TOA byte. For example: SIM ADN extension fields 811 */ 812 813 public static String 814 calledPartyBCDFragmentToString(byte [] bytes, int offset, int length) { 815 StringBuilder ret = new StringBuilder(length * 2); 816 817 internalCalledPartyBCDFragmentToString(ret, bytes, offset, length); 818 819 return ret.toString(); 820 } 821 822 /** returns 0 on invalid value */ 823 private static char 824 bcdToChar(byte b) { 825 if (b < 0xa) { 826 return (char)('0' + b); 827 } else switch (b) { 828 case 0xa: return '*'; 829 case 0xb: return '#'; 830 case 0xc: return PAUSE; 831 case 0xd: return WILD; 832 833 default: return 0; 834 } 835 } 836 837 private static int 838 charToBCD(char c) { 839 if (c >= '0' && c <= '9') { 840 return c - '0'; 841 } else if (c == '*') { 842 return 0xa; 843 } else if (c == '#') { 844 return 0xb; 845 } else if (c == PAUSE) { 846 return 0xc; 847 } else if (c == WILD) { 848 return 0xd; 849 } else { 850 throw new RuntimeException ("invalid char for BCD " + c); 851 } 852 } 853 854 /** 855 * Return true iff the network portion of <code>address</code> is, 856 * as far as we can tell on the device, suitable for use as an SMS 857 * destination address. 858 */ 859 public static boolean isWellFormedSmsAddress(String address) { 860 String networkPortion = 861 PhoneNumberUtils.extractNetworkPortion(address); 862 863 return (!(networkPortion.equals("+") 864 || TextUtils.isEmpty(networkPortion))) 865 && isDialable(networkPortion); 866 } 867 868 public static boolean isGlobalPhoneNumber(String phoneNumber) { 869 if (TextUtils.isEmpty(phoneNumber)) { 870 return false; 871 } 872 873 Matcher match = GLOBAL_PHONE_NUMBER_PATTERN.matcher(phoneNumber); 874 return match.matches(); 875 } 876 877 private static boolean isDialable(String address) { 878 for (int i = 0, count = address.length(); i < count; i++) { 879 if (!isDialable(address.charAt(i))) { 880 return false; 881 } 882 } 883 return true; 884 } 885 886 private static boolean isNonSeparator(String address) { 887 for (int i = 0, count = address.length(); i < count; i++) { 888 if (!isNonSeparator(address.charAt(i))) { 889 return false; 890 } 891 } 892 return true; 893 } 894 /** 895 * Note: calls extractNetworkPortion(), so do not use for 896 * SIM EF[ADN] style records 897 * 898 * Returns null if network portion is empty. 899 */ 900 public static byte[] 901 networkPortionToCalledPartyBCD(String s) { 902 String networkPortion = extractNetworkPortion(s); 903 return numberToCalledPartyBCDHelper(networkPortion, false); 904 } 905 906 /** 907 * Same as {@link #networkPortionToCalledPartyBCD}, but includes a 908 * one-byte length prefix. 909 */ 910 public static byte[] 911 networkPortionToCalledPartyBCDWithLength(String s) { 912 String networkPortion = extractNetworkPortion(s); 913 return numberToCalledPartyBCDHelper(networkPortion, true); 914 } 915 916 /** 917 * Convert a dialing number to BCD byte array 918 * 919 * @param number dialing number string 920 * if the dialing number starts with '+', set to internationl TOA 921 * @return BCD byte array 922 */ 923 public static byte[] 924 numberToCalledPartyBCD(String number) { 925 return numberToCalledPartyBCDHelper(number, false); 926 } 927 928 /** 929 * If includeLength is true, prepend a one-byte length value to 930 * the return array. 931 */ 932 private static byte[] 933 numberToCalledPartyBCDHelper(String number, boolean includeLength) { 934 int numberLenReal = number.length(); 935 int numberLenEffective = numberLenReal; 936 boolean hasPlus = number.indexOf('+') != -1; 937 if (hasPlus) numberLenEffective--; 938 939 if (numberLenEffective == 0) return null; 940 941 int resultLen = (numberLenEffective + 1) / 2; // Encoded numbers require only 4 bits each. 942 int extraBytes = 1; // Prepended TOA byte. 943 if (includeLength) extraBytes++; // Optional prepended length byte. 944 resultLen += extraBytes; 945 946 byte[] result = new byte[resultLen]; 947 948 int digitCount = 0; 949 for (int i = 0; i < numberLenReal; i++) { 950 char c = number.charAt(i); 951 if (c == '+') continue; 952 int shift = ((digitCount & 0x01) == 1) ? 4 : 0; 953 result[extraBytes + (digitCount >> 1)] |= (byte)((charToBCD(c) & 0x0F) << shift); 954 digitCount++; 955 } 956 957 // 1-fill any trailing odd nibble/quartet. 958 if ((digitCount & 0x01) == 1) result[extraBytes + (digitCount >> 1)] |= 0xF0; 959 960 int offset = 0; 961 if (includeLength) result[offset++] = (byte)(resultLen - 1); 962 result[offset] = (byte)(hasPlus ? TOA_International : TOA_Unknown); 963 964 return result; 965 } 966 967 //================ Number formatting ========================= 968 969 /** The current locale is unknown, look for a country code or don't format */ 970 public static final int FORMAT_UNKNOWN = 0; 971 /** NANP formatting */ 972 public static final int FORMAT_NANP = 1; 973 /** Japanese formatting */ 974 public static final int FORMAT_JAPAN = 2; 975 976 /** List of country codes for countries that use the NANP */ 977 private static final String[] NANP_COUNTRIES = new String[] { 978 "US", // United States 979 "CA", // Canada 980 "AS", // American Samoa 981 "AI", // Anguilla 982 "AG", // Antigua and Barbuda 983 "BS", // Bahamas 984 "BB", // Barbados 985 "BM", // Bermuda 986 "VG", // British Virgin Islands 987 "KY", // Cayman Islands 988 "DM", // Dominica 989 "DO", // Dominican Republic 990 "GD", // Grenada 991 "GU", // Guam 992 "JM", // Jamaica 993 "PR", // Puerto Rico 994 "MS", // Montserrat 995 "MP", // Northern Mariana Islands 996 "KN", // Saint Kitts and Nevis 997 "LC", // Saint Lucia 998 "VC", // Saint Vincent and the Grenadines 999 "TT", // Trinidad and Tobago 1000 "TC", // Turks and Caicos Islands 1001 "VI", // U.S. Virgin Islands 1002 }; 1003 1004 /** 1005 * Breaks the given number down and formats it according to the rules 1006 * for the country the number is from. 1007 * 1008 * @param source the phone number to format 1009 * @return a locally acceptable formatting of the input, or the raw input if 1010 * formatting rules aren't known for the number 1011 */ 1012 public static String formatNumber(String source) { 1013 SpannableStringBuilder text = new SpannableStringBuilder(source); 1014 formatNumber(text, getFormatTypeForLocale(Locale.getDefault())); 1015 return text.toString(); 1016 } 1017 1018 /** 1019 * Returns the phone number formatting type for the given locale. 1020 * 1021 * @param locale The locale of interest, usually {@link Locale#getDefault()} 1022 * @return the formatting type for the given locale, or FORMAT_UNKNOWN if the formatting 1023 * rules are not known for the given locale 1024 */ 1025 public static int getFormatTypeForLocale(Locale locale) { 1026 String country = locale.getCountry(); 1027 1028 return getFormatTypeFromCountryCode(country); 1029 } 1030 1031 /** 1032 * Formats a phone number in-place. Currently only supports NANP formatting. 1033 * 1034 * @param text The number to be formatted, will be modified with the formatting 1035 * @param defaultFormattingType The default formatting rules to apply if the number does 1036 * not begin with +<country_code> 1037 */ 1038 public static void formatNumber(Editable text, int defaultFormattingType) { 1039 int formatType = defaultFormattingType; 1040 1041 if (text.length() > 2 && text.charAt(0) == '+') { 1042 if (text.charAt(1) == '1') { 1043 formatType = FORMAT_NANP; 1044 } else if (text.length() >= 3 && text.charAt(1) == '8' 1045 && text.charAt(2) == '1') { 1046 formatType = FORMAT_JAPAN; 1047 } else { 1048 return; 1049 } 1050 } 1051 1052 switch (formatType) { 1053 case FORMAT_NANP: 1054 formatNanpNumber(text); 1055 return; 1056 case FORMAT_JAPAN: 1057 formatJapaneseNumber(text); 1058 return; 1059 } 1060 } 1061 1062 private static final int NANP_STATE_DIGIT = 1; 1063 private static final int NANP_STATE_PLUS = 2; 1064 private static final int NANP_STATE_ONE = 3; 1065 private static final int NANP_STATE_DASH = 4; 1066 1067 /** 1068 * Formats a phone number in-place using the NANP formatting rules. Numbers will be formatted 1069 * as: 1070 * 1071 * <p><code> 1072 * xxxxx 1073 * xxx-xxxx 1074 * xxx-xxx-xxxx 1075 * 1-xxx-xxx-xxxx 1076 * +1-xxx-xxx-xxxx 1077 * </code></p> 1078 * 1079 * @param text the number to be formatted, will be modified with the formatting 1080 */ 1081 public static void formatNanpNumber(Editable text) { 1082 int length = text.length(); 1083 if (length > "+1-nnn-nnn-nnnn".length()) { 1084 // The string is too long to be formatted 1085 return; 1086 } else if (length <= 5) { 1087 // The string is either a shortcode or too short to be formatted 1088 return; 1089 } 1090 1091 CharSequence saved = text.subSequence(0, length); 1092 1093 // Strip the dashes first, as we're going to add them back 1094 int p = 0; 1095 while (p < text.length()) { 1096 if (text.charAt(p) == '-') { 1097 text.delete(p, p + 1); 1098 } else { 1099 p++; 1100 } 1101 } 1102 length = text.length(); 1103 1104 // When scanning the number we record where dashes need to be added, 1105 // if they're non-0 at the end of the scan the dashes will be added in 1106 // the proper places. 1107 int dashPositions[] = new int[3]; 1108 int numDashes = 0; 1109 1110 int state = NANP_STATE_DIGIT; 1111 int numDigits = 0; 1112 for (int i = 0; i < length; i++) { 1113 char c = text.charAt(i); 1114 switch (c) { 1115 case '1': 1116 if (numDigits == 0 || state == NANP_STATE_PLUS) { 1117 state = NANP_STATE_ONE; 1118 break; 1119 } 1120 // fall through 1121 case '2': 1122 case '3': 1123 case '4': 1124 case '5': 1125 case '6': 1126 case '7': 1127 case '8': 1128 case '9': 1129 case '0': 1130 if (state == NANP_STATE_PLUS) { 1131 // Only NANP number supported for now 1132 text.replace(0, length, saved); 1133 return; 1134 } else if (state == NANP_STATE_ONE) { 1135 // Found either +1 or 1, follow it up with a dash 1136 dashPositions[numDashes++] = i; 1137 } else if (state != NANP_STATE_DASH && (numDigits == 3 || numDigits == 6)) { 1138 // Found a digit that should be after a dash that isn't 1139 dashPositions[numDashes++] = i; 1140 } 1141 state = NANP_STATE_DIGIT; 1142 numDigits++; 1143 break; 1144 1145 case '-': 1146 state = NANP_STATE_DASH; 1147 break; 1148 1149 case '+': 1150 if (i == 0) { 1151 // Plus is only allowed as the first character 1152 state = NANP_STATE_PLUS; 1153 break; 1154 } 1155 // Fall through 1156 default: 1157 // Unknown character, bail on formatting 1158 text.replace(0, length, saved); 1159 return; 1160 } 1161 } 1162 1163 if (numDigits == 7) { 1164 // With 7 digits we want xxx-xxxx, not xxx-xxx-x 1165 numDashes--; 1166 } 1167 1168 // Actually put the dashes in place 1169 for (int i = 0; i < numDashes; i++) { 1170 int pos = dashPositions[i]; 1171 text.replace(pos + i, pos + i, "-"); 1172 } 1173 1174 // Remove trailing dashes 1175 int len = text.length(); 1176 while (len > 0) { 1177 if (text.charAt(len - 1) == '-') { 1178 text.delete(len - 1, len); 1179 len--; 1180 } else { 1181 break; 1182 } 1183 } 1184 } 1185 1186 /** 1187 * Formats a phone number in-place using the Japanese formatting rules. 1188 * Numbers will be formatted as: 1189 * 1190 * <p><code> 1191 * 03-xxxx-xxxx 1192 * 090-xxxx-xxxx 1193 * 0120-xxx-xxx 1194 * +81-3-xxxx-xxxx 1195 * +81-90-xxxx-xxxx 1196 * </code></p> 1197 * 1198 * @param text the number to be formatted, will be modified with 1199 * the formatting 1200 */ 1201 public static void formatJapaneseNumber(Editable text) { 1202 JapanesePhoneNumberFormatter.format(text); 1203 } 1204 1205 // Three and four digit phone numbers for either special services, 1206 // or 3-6 digit addresses from the network (eg carrier-originated SMS messages) should 1207 // not match. 1208 // 1209 // This constant used to be 5, but SMS short codes has increased in length and 1210 // can be easily 6 digits now days. Most countries have SMS short code length between 1211 // 3 to 6 digits. The exceptions are 1212 // 1213 // Australia: Short codes are six or eight digits in length, starting with the prefix "19" 1214 // followed by an additional four or six digits and two. 1215 // Czech Republic: Codes are seven digits in length for MO and five (not billed) or 1216 // eight (billed) for MT direction 1217 // 1218 // see http://en.wikipedia.org/wiki/Short_code#Regional_differences for reference 1219 // 1220 // However, in order to loose match 650-555-1212 and 555-1212, we need to set the min match 1221 // to 7. 1222 static final int MIN_MATCH = 7; 1223 1224 /** 1225 * isEmergencyNumber: checks a given number against the list of 1226 * emergency numbers provided by the RIL and SIM card. 1227 * 1228 * @param number the number to look up. 1229 * @return if the number is in the list of emergency numbers 1230 * listed in the ril / sim, then return true, otherwise false. 1231 */ 1232 public static boolean isEmergencyNumber(String number) { 1233 // If the number passed in is null, just return false: 1234 if (number == null) return false; 1235 1236 // Strip the separators from the number before comparing it 1237 // to the list. 1238 number = extractNetworkPortion(number); 1239 1240 // retrieve the list of emergency numbers 1241 String numbers = SystemProperties.get("ro.ril.ecclist"); 1242 1243 if (!TextUtils.isEmpty(numbers)) { 1244 // searches through the comma-separated list for a match, 1245 // return true if one is found. 1246 for (String emergencyNum : numbers.split(",")) { 1247 if (emergencyNum.equals(number)) { 1248 return true; 1249 } 1250 } 1251 // no matches found against the list! 1252 return false; 1253 } 1254 1255 //no ecclist system property, so use our own list. 1256 return (number.equals("112") || number.equals("911")); 1257 } 1258 1259 /** 1260 * isVoiceMailNumber: checks a given number against the voicemail 1261 * number provided by the RIL and SIM card. The caller must have 1262 * the READ_PHONE_STATE credential. 1263 * 1264 * @param number the number to look up. 1265 * @return true if the number is in the list of voicemail. False 1266 * otherwise, including if the caller does not have the permission 1267 * to read the VM number. 1268 * @hide TODO: pending API Council approval 1269 */ 1270 public static boolean isVoiceMailNumber(String number) { 1271 String vmNumber; 1272 1273 try { 1274 vmNumber = TelephonyManager.getDefault().getVoiceMailNumber(); 1275 } catch (SecurityException ex) { 1276 return false; 1277 } 1278 1279 // Strip the separators from the number before comparing it 1280 // to the list. 1281 number = extractNetworkPortion(number); 1282 1283 // compare tolerates null so we need to make sure that we 1284 // don't return true when both are null. 1285 return !TextUtils.isEmpty(number) && compare(number, vmNumber); 1286 } 1287 1288 /** 1289 * Translates any alphabetic letters (i.e. [A-Za-z]) in the 1290 * specified phone number into the equivalent numeric digits, 1291 * according to the phone keypad letter mapping described in 1292 * ITU E.161 and ISO/IEC 9995-8. 1293 * 1294 * @return the input string, with alpha letters converted to numeric 1295 * digits using the phone keypad letter mapping. For example, 1296 * an input of "1-800-GOOG-411" will return "1-800-4664-411". 1297 */ 1298 public static String convertKeypadLettersToDigits(String input) { 1299 if (input == null) { 1300 return input; 1301 } 1302 int len = input.length(); 1303 if (len == 0) { 1304 return input; 1305 } 1306 1307 char[] out = input.toCharArray(); 1308 1309 for (int i = 0; i < len; i++) { 1310 char c = out[i]; 1311 // If this char isn't in KEYPAD_MAP at all, just leave it alone. 1312 out[i] = (char) KEYPAD_MAP.get(c, c); 1313 } 1314 1315 return new String(out); 1316 } 1317 1318 /** 1319 * The phone keypad letter mapping (see ITU E.161 or ISO/IEC 9995-8.) 1320 * TODO: This should come from a resource. 1321 */ 1322 private static final SparseIntArray KEYPAD_MAP = new SparseIntArray(); 1323 static { 1324 KEYPAD_MAP.put('a', '2'); KEYPAD_MAP.put('b', '2'); KEYPAD_MAP.put('c', '2'); 1325 KEYPAD_MAP.put('A', '2'); KEYPAD_MAP.put('B', '2'); KEYPAD_MAP.put('C', '2'); 1326 1327 KEYPAD_MAP.put('d', '3'); KEYPAD_MAP.put('e', '3'); KEYPAD_MAP.put('f', '3'); 1328 KEYPAD_MAP.put('D', '3'); KEYPAD_MAP.put('E', '3'); KEYPAD_MAP.put('F', '3'); 1329 1330 KEYPAD_MAP.put('g', '4'); KEYPAD_MAP.put('h', '4'); KEYPAD_MAP.put('i', '4'); 1331 KEYPAD_MAP.put('G', '4'); KEYPAD_MAP.put('H', '4'); KEYPAD_MAP.put('I', '4'); 1332 1333 KEYPAD_MAP.put('j', '5'); KEYPAD_MAP.put('k', '5'); KEYPAD_MAP.put('l', '5'); 1334 KEYPAD_MAP.put('J', '5'); KEYPAD_MAP.put('K', '5'); KEYPAD_MAP.put('L', '5'); 1335 1336 KEYPAD_MAP.put('m', '6'); KEYPAD_MAP.put('n', '6'); KEYPAD_MAP.put('o', '6'); 1337 KEYPAD_MAP.put('M', '6'); KEYPAD_MAP.put('N', '6'); KEYPAD_MAP.put('O', '6'); 1338 1339 KEYPAD_MAP.put('p', '7'); KEYPAD_MAP.put('q', '7'); KEYPAD_MAP.put('r', '7'); KEYPAD_MAP.put('s', '7'); 1340 KEYPAD_MAP.put('P', '7'); KEYPAD_MAP.put('Q', '7'); KEYPAD_MAP.put('R', '7'); KEYPAD_MAP.put('S', '7'); 1341 1342 KEYPAD_MAP.put('t', '8'); KEYPAD_MAP.put('u', '8'); KEYPAD_MAP.put('v', '8'); 1343 KEYPAD_MAP.put('T', '8'); KEYPAD_MAP.put('U', '8'); KEYPAD_MAP.put('V', '8'); 1344 1345 KEYPAD_MAP.put('w', '9'); KEYPAD_MAP.put('x', '9'); KEYPAD_MAP.put('y', '9'); KEYPAD_MAP.put('z', '9'); 1346 KEYPAD_MAP.put('W', '9'); KEYPAD_MAP.put('X', '9'); KEYPAD_MAP.put('Y', '9'); KEYPAD_MAP.put('Z', '9'); 1347 } 1348 1349 //================ Plus Code formatting ========================= 1350 private static final char PLUS_SIGN_CHAR = '+'; 1351 private static final String PLUS_SIGN_STRING = "+"; 1352 private static final String NANP_IDP_STRING = "011"; 1353 private static final int NANP_LENGTH = 10; 1354 1355 /** 1356 * This function checks if there is a plus sign (+) in the passed-in dialing number. 1357 * If there is, it processes the plus sign based on the default telephone 1358 * numbering plan of the system when the phone is activated and the current 1359 * telephone numbering plan of the system that the phone is camped on. 1360 * Currently, we only support the case that the default and current telephone 1361 * numbering plans are North American Numbering Plan(NANP). 1362 * 1363 * The passed-in dialStr should only contain the valid format as described below, 1364 * 1) the 1st character in the dialStr should be one of the really dialable 1365 * characters listed below 1366 * ISO-LATIN characters 0-9, *, # , + 1367 * 2) the dialStr should already strip out the separator characters, 1368 * every character in the dialStr should be one of the non separator characters 1369 * listed below 1370 * ISO-LATIN characters 0-9, *, # , +, WILD, WAIT, PAUSE 1371 * 1372 * Otherwise, this function returns the dial string passed in 1373 * 1374 * @param dialStr the original dial string 1375 * @return the converted dial string if the current/default countries belong to NANP, 1376 * and if there is the "+" in the original dial string. Otherwise, the original dial 1377 * string returns. 1378 * 1379 * This API is for CDMA only 1380 * 1381 * @hide TODO: pending API Council approval 1382 */ 1383 public static String cdmaCheckAndProcessPlusCode(String dialStr) { 1384 if (!TextUtils.isEmpty(dialStr)) { 1385 if (isReallyDialable(dialStr.charAt(0)) && 1386 isNonSeparator(dialStr)) { 1387 String currIso = SystemProperties.get(PROPERTY_OPERATOR_ISO_COUNTRY, ""); 1388 String defaultIso = SystemProperties.get(PROPERTY_ICC_OPERATOR_ISO_COUNTRY, ""); 1389 if (!TextUtils.isEmpty(currIso) && !TextUtils.isEmpty(defaultIso)) { 1390 return cdmaCheckAndProcessPlusCodeByNumberFormat(dialStr, 1391 getFormatTypeFromCountryCode(currIso), 1392 getFormatTypeFromCountryCode(defaultIso)); 1393 } 1394 } 1395 } 1396 return dialStr; 1397 } 1398 1399 /** 1400 * This function should be called from checkAndProcessPlusCode only 1401 * And it is used for test purpose also. 1402 * 1403 * It checks the dial string by looping through the network portion, 1404 * post dial portion 1, post dial porting 2, etc. If there is any 1405 * plus sign, then process the plus sign. 1406 * Currently, this function supports the plus sign conversion within NANP only. 1407 * Specifically, it handles the plus sign in the following ways: 1408 * 1)+1NANP,remove +, e.g. 1409 * +18475797000 is converted to 18475797000, 1410 * 2)+NANP or +non-NANP Numbers,replace + with the current NANP IDP, e.g, 1411 * +8475797000 is converted to 0118475797000, 1412 * +11875767800 is converted to 01111875767800 1413 * 3)+1NANP in post dial string(s), e.g. 1414 * 8475797000;+18475231753 is converted to 8475797000;18475231753 1415 * 1416 * 1417 * @param dialStr the original dial string 1418 * @param currFormat the numbering system of the current country that the phone is camped on 1419 * @param defaultFormat the numbering system of the country that the phone is activated on 1420 * @return the converted dial string if the current/default countries belong to NANP, 1421 * and if there is the "+" in the original dial string. Otherwise, the original dial 1422 * string returns. 1423 * 1424 * @hide 1425 */ 1426 public static String 1427 cdmaCheckAndProcessPlusCodeByNumberFormat(String dialStr,int currFormat,int defaultFormt) { 1428 String retStr = dialStr; 1429 1430 // Checks if the plus sign character is in the passed-in dial string 1431 if (dialStr != null && 1432 dialStr.lastIndexOf(PLUS_SIGN_STRING) != -1) { 1433 // Format the string based on the rules for the country the number is from, 1434 // and the current country the phone is camped on. 1435 if ((currFormat == defaultFormt) && (currFormat == FORMAT_NANP)) { 1436 // Handle case where default and current telephone numbering plans are NANP. 1437 String postDialStr = null; 1438 String tempDialStr = dialStr; 1439 1440 // Sets the retStr to null since the conversion will be performed below. 1441 retStr = null; 1442 if (DBG) log("checkAndProcessPlusCode,dialStr=" + dialStr); 1443 // This routine is to process the plus sign in the dial string by loop through 1444 // the network portion, post dial portion 1, post dial portion 2... etc. if 1445 // applied 1446 do { 1447 String networkDialStr; 1448 networkDialStr = extractNetworkPortion(tempDialStr); 1449 // Handles the conversion within NANP 1450 networkDialStr = processPlusCodeWithinNanp(networkDialStr); 1451 1452 // Concatenates the string that is converted from network portion 1453 if (!TextUtils.isEmpty(networkDialStr)) { 1454 if (retStr == null) { 1455 retStr = networkDialStr; 1456 } else { 1457 retStr = retStr.concat(networkDialStr); 1458 } 1459 } else { 1460 // This should never happen since we checked the if dialStr is null 1461 // and if it contains the plus sign in the beginning of this function. 1462 // The plus sign is part of the network portion. 1463 Log.e("checkAndProcessPlusCode: null newDialStr", networkDialStr); 1464 return dialStr; 1465 } 1466 postDialStr = extractPostDialPortion(tempDialStr); 1467 if (!TextUtils.isEmpty(postDialStr)) { 1468 int dialableIndex = findDialableIndexFromPostDialStr(postDialStr); 1469 1470 // dialableIndex should always be greater than 0 1471 if (dialableIndex >= 1) { 1472 retStr = appendPwCharBackToOrigDialStr(dialableIndex, 1473 retStr,postDialStr); 1474 // Skips the P/W character, extracts the dialable portion 1475 tempDialStr = postDialStr.substring(dialableIndex); 1476 } else { 1477 // Non-dialable character such as P/W should not be at the end of 1478 // the dial string after P/W processing in CdmaConnection.java 1479 // Set the postDialStr to "" to break out of the loop 1480 if (dialableIndex < 0) { 1481 postDialStr = ""; 1482 } 1483 Log.e("wrong postDialStr=", postDialStr); 1484 } 1485 } 1486 if (DBG) log("checkAndProcessPlusCode,postDialStr=" + postDialStr); 1487 } while (!TextUtils.isEmpty(postDialStr) && !TextUtils.isEmpty(tempDialStr)); 1488 } else { 1489 // TODO: Support NANP international conversion and other telephone numbering plans. 1490 // Currently the phone is never used in non-NANP system, so return the original 1491 // dial string. 1492 Log.e("checkAndProcessPlusCode:non-NANP not supported", dialStr); 1493 } 1494 } 1495 return retStr; 1496 } 1497 1498 // This function gets the default international dialing prefix 1499 private static String getDefaultIdp( ) { 1500 String ps = null; 1501 SystemProperties.get(PROPERTY_IDP_STRING, ps); 1502 if (TextUtils.isEmpty(ps)) { 1503 ps = NANP_IDP_STRING; 1504 } 1505 return ps; 1506 } 1507 1508 private static boolean isTwoToNine (char c) { 1509 if (c >= '2' && c <= '9') { 1510 return true; 1511 } else { 1512 return false; 1513 } 1514 } 1515 1516 private static int getFormatTypeFromCountryCode (String country) { 1517 // Check for the NANP countries 1518 int length = NANP_COUNTRIES.length; 1519 for (int i = 0; i < length; i++) { 1520 if (NANP_COUNTRIES[i].compareToIgnoreCase(country) == 0) { 1521 return FORMAT_NANP; 1522 } 1523 } 1524 if ("jp".compareToIgnoreCase(country) == 0) { 1525 return FORMAT_JAPAN; 1526 } 1527 return FORMAT_UNKNOWN; 1528 } 1529 1530 /** 1531 * This function checks if the passed in string conforms to the NANP format 1532 * i.e. NXX-NXX-XXXX, N is any digit 2-9 and X is any digit 0-9 1533 */ 1534 private static boolean isNanp (String dialStr) { 1535 boolean retVal = false; 1536 if (dialStr != null) { 1537 if (dialStr.length() == NANP_LENGTH) { 1538 if (isTwoToNine(dialStr.charAt(0)) && 1539 isTwoToNine(dialStr.charAt(3))) { 1540 retVal = true; 1541 for (int i=1; i<NANP_LENGTH; i++ ) { 1542 char c=dialStr.charAt(i); 1543 if (!PhoneNumberUtils.isISODigit(c)) { 1544 retVal = false; 1545 break; 1546 } 1547 } 1548 } 1549 } 1550 } else { 1551 Log.e("isNanp: null dialStr passed in", dialStr); 1552 } 1553 return retVal; 1554 } 1555 1556 /** 1557 * This function checks if the passed in string conforms to 1-NANP format 1558 */ 1559 private static boolean isOneNanp(String dialStr) { 1560 boolean retVal = false; 1561 if (dialStr != null) { 1562 String newDialStr = dialStr.substring(1); 1563 if ((dialStr.charAt(0) == '1') && isNanp(newDialStr)) { 1564 retVal = true; 1565 } 1566 } else { 1567 Log.e("isOneNanp: null dialStr passed in", dialStr); 1568 } 1569 return retVal; 1570 } 1571 1572 /** 1573 * This function handles the plus code conversion within NANP CDMA network 1574 * If the number format is 1575 * 1)+1NANP,remove +, 1576 * 2)other than +1NANP, any + numbers,replace + with the current IDP 1577 */ 1578 private static String processPlusCodeWithinNanp(String networkDialStr) { 1579 String retStr = networkDialStr; 1580 1581 if (DBG) log("processPlusCodeWithinNanp,networkDialStr=" + networkDialStr); 1582 // If there is a plus sign at the beginning of the dial string, 1583 // Convert the plus sign to the default IDP since it's an international number 1584 if (networkDialStr != null & 1585 networkDialStr.charAt(0) == PLUS_SIGN_CHAR && 1586 networkDialStr.length() > 1) { 1587 String newStr = networkDialStr.substring(1); 1588 if (isOneNanp(newStr)) { 1589 // Remove the leading plus sign 1590 retStr = newStr; 1591 } else { 1592 String idpStr = getDefaultIdp(); 1593 // Replaces the plus sign with the default IDP 1594 retStr = networkDialStr.replaceFirst("[+]", idpStr); 1595 } 1596 } 1597 if (DBG) log("processPlusCodeWithinNanp,retStr=" + retStr); 1598 return retStr; 1599 } 1600 1601 // This function finds the index of the dialable character(s) 1602 // in the post dial string 1603 private static int findDialableIndexFromPostDialStr(String postDialStr) { 1604 for (int index = 0;index < postDialStr.length();index++) { 1605 char c = postDialStr.charAt(index); 1606 if (isReallyDialable(c)) { 1607 return index; 1608 } 1609 } 1610 return -1; 1611 } 1612 1613 // This function appends the non-diablable P/W character to the original 1614 // dial string based on the dialable index passed in 1615 private static String 1616 appendPwCharBackToOrigDialStr(int dialableIndex,String origStr, String dialStr) { 1617 String retStr; 1618 1619 // There is only 1 P/W character before the dialable characters 1620 if (dialableIndex == 1) { 1621 StringBuilder ret = new StringBuilder(origStr); 1622 ret = ret.append(dialStr.charAt(0)); 1623 retStr = ret.toString(); 1624 } else { 1625 // It means more than 1 P/W characters in the post dial string, 1626 // appends to retStr 1627 String nonDigitStr = dialStr.substring(0,dialableIndex); 1628 retStr = origStr.concat(nonDigitStr); 1629 } 1630 return retStr; 1631 } 1632 1633 //===== Begining of utility methods used in compareLoosely() ===== 1634 1635 /** 1636 * Phone numbers are stored in "lookup" form in the database 1637 * as reversed strings to allow for caller ID lookup 1638 * 1639 * This method takes a phone number and makes a valid SQL "LIKE" 1640 * string that will match the lookup form 1641 * 1642 */ 1643 /** all of a up to len must be an international prefix or 1644 * separators/non-dialing digits 1645 */ 1646 private static boolean 1647 matchIntlPrefix(String a, int len) { 1648 /* '([^0-9*#+pwn]\+[^0-9*#+pwn] | [^0-9*#+pwn]0(0|11)[^0-9*#+pwn] )$' */ 1649 /* 0 1 2 3 45 */ 1650 1651 int state = 0; 1652 for (int i = 0 ; i < len ; i++) { 1653 char c = a.charAt(i); 1654 1655 switch (state) { 1656 case 0: 1657 if (c == '+') state = 1; 1658 else if (c == '0') state = 2; 1659 else if (isNonSeparator(c)) return false; 1660 break; 1661 1662 case 2: 1663 if (c == '0') state = 3; 1664 else if (c == '1') state = 4; 1665 else if (isNonSeparator(c)) return false; 1666 break; 1667 1668 case 4: 1669 if (c == '1') state = 5; 1670 else if (isNonSeparator(c)) return false; 1671 break; 1672 1673 default: 1674 if (isNonSeparator(c)) return false; 1675 break; 1676 1677 } 1678 } 1679 1680 return state == 1 || state == 3 || state == 5; 1681 } 1682 1683 /** all of 'a' up to len must be a (+|00|011)country code) 1684 * We're fast and loose with the country code. Any \d{1,3} matches */ 1685 private static boolean 1686 matchIntlPrefixAndCC(String a, int len) { 1687 /* [^0-9*#+pwn]*(\+|0(0|11)\d\d?\d? [^0-9*#+pwn] $ */ 1688 /* 0 1 2 3 45 6 7 8 */ 1689 1690 int state = 0; 1691 for (int i = 0 ; i < len ; i++ ) { 1692 char c = a.charAt(i); 1693 1694 switch (state) { 1695 case 0: 1696 if (c == '+') state = 1; 1697 else if (c == '0') state = 2; 1698 else if (isNonSeparator(c)) return false; 1699 break; 1700 1701 case 2: 1702 if (c == '0') state = 3; 1703 else if (c == '1') state = 4; 1704 else if (isNonSeparator(c)) return false; 1705 break; 1706 1707 case 4: 1708 if (c == '1') state = 5; 1709 else if (isNonSeparator(c)) return false; 1710 break; 1711 1712 case 1: 1713 case 3: 1714 case 5: 1715 if (isISODigit(c)) state = 6; 1716 else if (isNonSeparator(c)) return false; 1717 break; 1718 1719 case 6: 1720 case 7: 1721 if (isISODigit(c)) state++; 1722 else if (isNonSeparator(c)) return false; 1723 break; 1724 1725 default: 1726 if (isNonSeparator(c)) return false; 1727 } 1728 } 1729 1730 return state == 6 || state == 7 || state == 8; 1731 } 1732 1733 /** all of 'a' up to len must match non-US trunk prefix ('0') */ 1734 private static boolean 1735 matchTrunkPrefix(String a, int len) { 1736 boolean found; 1737 1738 found = false; 1739 1740 for (int i = 0 ; i < len ; i++) { 1741 char c = a.charAt(i); 1742 1743 if (c == '0' && !found) { 1744 found = true; 1745 } else if (isNonSeparator(c)) { 1746 return false; 1747 } 1748 } 1749 1750 return found; 1751 } 1752 1753 //===== End of utility methods used only in compareLoosely() ===== 1754 1755 //===== Beggining of utility methods used only in compareStrictly() ==== 1756 1757 /* 1758 * If true, the number is country calling code. 1759 */ 1760 private static final boolean COUNTLY_CALLING_CALL[] = { 1761 true, true, false, false, false, false, false, true, false, false, 1762 false, false, false, false, false, false, false, false, false, false, 1763 true, false, false, false, false, false, false, true, true, false, 1764 true, true, true, true, true, false, true, false, false, true, 1765 true, false, false, true, true, true, true, true, true, true, 1766 false, true, true, true, true, true, true, true, true, false, 1767 true, true, true, true, true, true, true, false, false, false, 1768 false, false, false, false, false, false, false, false, false, false, 1769 false, true, true, true, true, false, true, false, false, true, 1770 true, true, true, true, true, true, false, false, true, false, 1771 }; 1772 private static final int CCC_LENGTH = COUNTLY_CALLING_CALL.length; 1773 1774 /** 1775 * @return true when input is valid Country Calling Code. 1776 */ 1777 private static boolean isCountryCallingCode(int countryCallingCodeCandidate) { 1778 return countryCallingCodeCandidate > 0 && countryCallingCodeCandidate < CCC_LENGTH && 1779 COUNTLY_CALLING_CALL[countryCallingCodeCandidate]; 1780 } 1781 1782 /** 1783 * Returns interger corresponding to the input if input "ch" is 1784 * ISO-LATIN characters 0-9. 1785 * Returns -1 otherwise 1786 */ 1787 private static int tryGetISODigit(char ch) { 1788 if ('0' <= ch && ch <= '9') { 1789 return ch - '0'; 1790 } else { 1791 return -1; 1792 } 1793 } 1794 1795 private static class CountryCallingCodeAndNewIndex { 1796 public final int countryCallingCode; 1797 public final int newIndex; 1798 public CountryCallingCodeAndNewIndex(int countryCode, int newIndex) { 1799 this.countryCallingCode = countryCode; 1800 this.newIndex = newIndex; 1801 } 1802 } 1803 1804 /* 1805 * Note that this function does not strictly care the country calling code with 1806 * 3 length (like Morocco: +212), assuming it is enough to use the first two 1807 * digit to compare two phone numbers. 1808 */ 1809 private static CountryCallingCodeAndNewIndex tryGetCountryCallingCodeAndNewIndex( 1810 String str, boolean acceptThailandCase) { 1811 // Rough regexp: 1812 // ^[^0-9*#+]*((\+|0(0|11)\d\d?|166) [^0-9*#+] $ 1813 // 0 1 2 3 45 6 7 89 1814 // 1815 // In all the states, this function ignores separator characters. 1816 // "166" is the special case for the call from Thailand to the US. Uguu! 1817 int state = 0; 1818 int ccc = 0; 1819 final int length = str.length(); 1820 for (int i = 0 ; i < length ; i++ ) { 1821 char ch = str.charAt(i); 1822 switch (state) { 1823 case 0: 1824 if (ch == '+') state = 1; 1825 else if (ch == '0') state = 2; 1826 else if (ch == '1') { 1827 if (acceptThailandCase) { 1828 state = 8; 1829 } else { 1830 return null; 1831 } 1832 } else if (isDialable(ch)) { 1833 return null; 1834 } 1835 break; 1836 1837 case 2: 1838 if (ch == '0') state = 3; 1839 else if (ch == '1') state = 4; 1840 else if (isDialable(ch)) { 1841 return null; 1842 } 1843 break; 1844 1845 case 4: 1846 if (ch == '1') state = 5; 1847 else if (isDialable(ch)) { 1848 return null; 1849 } 1850 break; 1851 1852 case 1: 1853 case 3: 1854 case 5: 1855 case 6: 1856 case 7: 1857 { 1858 int ret = tryGetISODigit(ch); 1859 if (ret > 0) { 1860 ccc = ccc * 10 + ret; 1861 if (ccc >= 100 || isCountryCallingCode(ccc)) { 1862 return new CountryCallingCodeAndNewIndex(ccc, i + 1); 1863 } 1864 if (state == 1 || state == 3 || state == 5) { 1865 state = 6; 1866 } else { 1867 state++; 1868 } 1869 } else if (isDialable(ch)) { 1870 return null; 1871 } 1872 } 1873 break; 1874 case 8: 1875 if (ch == '6') state = 9; 1876 else if (isDialable(ch)) { 1877 return null; 1878 } 1879 break; 1880 case 9: 1881 if (ch == '6') { 1882 return new CountryCallingCodeAndNewIndex(66, i + 1); 1883 } else { 1884 return null; 1885 } 1886 default: 1887 return null; 1888 } 1889 } 1890 1891 return null; 1892 } 1893 1894 /** 1895 * Currently this function simply ignore the first digit assuming it is 1896 * trunk prefix. Actually trunk prefix is different in each country. 1897 * 1898 * e.g. 1899 * "+79161234567" equals "89161234567" (Russian trunk digit is 8) 1900 * "+33123456789" equals "0123456789" (French trunk digit is 0) 1901 * 1902 */ 1903 private static int tryGetTrunkPrefixOmittedIndex(String str, int currentIndex) { 1904 int length = str.length(); 1905 for (int i = currentIndex ; i < length ; i++) { 1906 final char ch = str.charAt(i); 1907 if (tryGetISODigit(ch) >= 0) { 1908 return i + 1; 1909 } else if (isDialable(ch)) { 1910 return -1; 1911 } 1912 } 1913 return -1; 1914 } 1915 1916 /** 1917 * Return true if the prefix of "str" is "ignorable". Here, "ignorable" means 1918 * that "str" has only one digit and separater characters. The one digit is 1919 * assumed to be trunk prefix. 1920 */ 1921 private static boolean checkPrefixIsIgnorable(final String str, 1922 int forwardIndex, int backwardIndex) { 1923 boolean trunk_prefix_was_read = false; 1924 while (backwardIndex >= forwardIndex) { 1925 if (tryGetISODigit(str.charAt(backwardIndex)) >= 0) { 1926 if (trunk_prefix_was_read) { 1927 // More than one digit appeared, meaning that "a" and "b" 1928 // is different. 1929 return false; 1930 } else { 1931 // Ignore just one digit, assuming it is trunk prefix. 1932 trunk_prefix_was_read = true; 1933 } 1934 } else if (isDialable(str.charAt(backwardIndex))) { 1935 // Trunk prefix is a digit, not "*", "#"... 1936 return false; 1937 } 1938 backwardIndex--; 1939 } 1940 1941 return true; 1942 } 1943 1944 //==== End of utility methods used only in compareStrictly() ===== 1945} 1946