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