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