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