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