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