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