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