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