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