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