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