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