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