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