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