PhoneNumberUtils.java revision 47633f86279bc1854660d95a0b02bfa5ff14ad04
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 android.content.Context; 20import android.content.Intent; 21import android.database.Cursor; 22import android.net.Uri; 23import android.os.SystemProperties; 24import android.provider.Contacts; 25import android.provider.ContactsContract; 26import android.text.Editable; 27import android.text.SpannableStringBuilder; 28import android.text.TextUtils; 29import android.util.Log; 30import android.util.SparseIntArray; 31 32import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_ISO_COUNTRY; 33import static com.android.internal.telephony.TelephonyProperties.PROPERTY_IDP_STRING; 34import static com.android.internal.telephony.TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY; 35 36import java.util.Locale; 37import java.util.regex.Matcher; 38import java.util.regex.Pattern; 39 40/** 41 * Various utilities for dealing with phone number strings. 42 */ 43public class PhoneNumberUtils 44{ 45 /* 46 * Special characters 47 * 48 * (See "What is a phone number?" doc) 49 * 'p' --- GSM pause character, same as comma 50 * 'n' --- GSM wild character 51 * 'w' --- GSM wait character 52 */ 53 public static final char PAUSE = ','; 54 public static final char WAIT = ';'; 55 public static final char WILD = 'N'; 56 57 /* 58 * TOA = TON + NPI 59 * See TS 24.008 section 10.5.4.7 for details. 60 * These are the only really useful TOA values 61 */ 62 public static final int TOA_International = 0x91; 63 public static final int TOA_Unknown = 0x81; 64 65 static final String LOG_TAG = "PhoneNumberUtils"; 66 private static final boolean DBG = false; 67 68 /* 69 * global-phone-number = ["+"] 1*( DIGIT / written-sep ) 70 * written-sep = ("-"/".") 71 */ 72 private static final Pattern GLOBAL_PHONE_NUMBER_PATTERN = 73 Pattern.compile("[\\+]?[0-9.-]+"); 74 75 /** True if c is ISO-LATIN characters 0-9 */ 76 public static boolean 77 isISODigit (char c) { 78 return c >= '0' && c <= '9'; 79 } 80 81 /** True if c is ISO-LATIN characters 0-9, *, # */ 82 public final static boolean 83 is12Key(char c) { 84 return (c >= '0' && c <= '9') || c == '*' || c == '#'; 85 } 86 87 /** True if c is ISO-LATIN characters 0-9, *, # , +, WILD */ 88 public final static boolean 89 isDialable(char c) { 90 return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+' || c == WILD; 91 } 92 93 /** True if c is ISO-LATIN characters 0-9, *, # , + (no WILD) */ 94 public final static boolean 95 isReallyDialable(char c) { 96 return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+'; 97 } 98 99 /** True if c is ISO-LATIN characters 0-9, *, # , +, WILD, WAIT, PAUSE */ 100 public final static boolean 101 isNonSeparator(char c) { 102 return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+' 103 || c == WILD || c == WAIT || c == PAUSE; 104 } 105 106 /** This any anything to the right of this char is part of the 107 * post-dial string (eg this is PAUSE or WAIT) 108 */ 109 public final static boolean 110 isStartsPostDial (char c) { 111 return c == PAUSE || c == WAIT; 112 } 113 114 /** Returns true if ch is not dialable or alpha char */ 115 private static boolean isSeparator(char ch) { 116 return !isDialable(ch) && !(('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z')); 117 } 118 119 /** Extracts the phone number from an Intent. 120 * 121 * @param intent the intent to get the number of 122 * @param context a context to use for database access 123 * 124 * @return the phone number that would be called by the intent, or 125 * <code>null</code> if the number cannot be found. 126 */ 127 public static String getNumberFromIntent(Intent intent, Context context) { 128 String number = null; 129 130 Uri uri = intent.getData(); 131 String scheme = uri.getScheme(); 132 133 if (scheme.equals("tel")) { 134 return uri.getSchemeSpecificPart(); 135 } 136 137 // TODO: We don't check for SecurityException here (requires 138 // READ_PHONE_STATE permission). 139 if (scheme.equals("voicemail")) { 140 return TelephonyManager.getDefault().getVoiceMailNumber(); 141 } 142 143 if (context == null) { 144 return null; 145 } 146 147 String type = intent.resolveType(context); 148 String phoneColumn = null; 149 150 // Correctly read out the phone entry based on requested provider 151 final String authority = uri.getAuthority(); 152 if (Contacts.AUTHORITY.equals(authority)) { 153 phoneColumn = Contacts.People.Phones.NUMBER; 154 } else if (ContactsContract.AUTHORITY.equals(authority)) { 155 phoneColumn = ContactsContract.CommonDataKinds.Phone.NUMBER; 156 } 157 158 final Cursor c = context.getContentResolver().query(uri, new String[] { 159 phoneColumn 160 }, null, null, null); 161 if (c != null) { 162 try { 163 if (c.moveToFirst()) { 164 number = c.getString(c.getColumnIndex(phoneColumn)); 165 } 166 } finally { 167 c.close(); 168 } 169 } 170 171 return number; 172 } 173 174 /** Extracts the network address portion and canonicalizes 175 * (filters out separators.) 176 * Network address portion is everything up to DTMF control digit 177 * separators (pause or wait), but without non-dialable characters. 178 * 179 * Please note that the GSM wild character is allowed in the result. 180 * This must be resolved before dialing. 181 * 182 * Allows + only in the first position in the result string. 183 * 184 * Returns null if phoneNumber == null 185 */ 186 public static String 187 extractNetworkPortion(String phoneNumber) { 188 if (phoneNumber == null) { 189 return null; 190 } 191 192 int len = phoneNumber.length(); 193 StringBuilder ret = new StringBuilder(len); 194 boolean firstCharAdded = false; 195 196 for (int i = 0; i < len; i++) { 197 char c = phoneNumber.charAt(i); 198 if (isDialable(c) && (c != '+' || !firstCharAdded)) { 199 firstCharAdded = true; 200 ret.append(c); 201 } else if (isStartsPostDial (c)) { 202 break; 203 } 204 } 205 206 return ret.toString(); 207 } 208 209 /** 210 * Strips separators from a phone number string. 211 * @param phoneNumber phone number to strip. 212 * @return phone string stripped of separators. 213 */ 214 public static String stripSeparators(String phoneNumber) { 215 if (phoneNumber == null) { 216 return null; 217 } 218 int len = phoneNumber.length(); 219 StringBuilder ret = new StringBuilder(len); 220 221 for (int i = 0; i < len; i++) { 222 char c = phoneNumber.charAt(i); 223 if (isNonSeparator(c)) { 224 ret.append(c); 225 } 226 } 227 228 return ret.toString(); 229 } 230 231 /** or -1 if both are negative */ 232 static private int 233 minPositive (int a, int b) { 234 if (a >= 0 && b >= 0) { 235 return (a < b) ? a : b; 236 } else if (a >= 0) { /* && b < 0 */ 237 return a; 238 } else if (b >= 0) { /* && a < 0 */ 239 return b; 240 } else { /* a < 0 && b < 0 */ 241 return -1; 242 } 243 } 244 245 private static void log(String msg) { 246 Log.d(LOG_TAG, msg); 247 } 248 /** index of the last character of the network portion 249 * (eg anything after is a post-dial string) 250 */ 251 static private int 252 indexOfLastNetworkChar(String a) { 253 int pIndex, wIndex; 254 int origLength; 255 int trimIndex; 256 257 origLength = a.length(); 258 259 pIndex = a.indexOf(PAUSE); 260 wIndex = a.indexOf(WAIT); 261 262 trimIndex = minPositive(pIndex, wIndex); 263 264 if (trimIndex < 0) { 265 return origLength - 1; 266 } else { 267 return trimIndex - 1; 268 } 269 } 270 271 /** 272 * Extracts the post-dial sequence of DTMF control digits, pauses, and 273 * waits. Strips separators. This string may be empty, but will not be null 274 * unless phoneNumber == null. 275 * 276 * Returns null if phoneNumber == null 277 */ 278 279 public static String 280 extractPostDialPortion(String phoneNumber) { 281 if (phoneNumber == null) return null; 282 283 int trimIndex; 284 StringBuilder ret = new StringBuilder(); 285 286 trimIndex = indexOfLastNetworkChar (phoneNumber); 287 288 for (int i = trimIndex + 1, s = phoneNumber.length() 289 ; i < s; i++ 290 ) { 291 char c = phoneNumber.charAt(i); 292 if (isNonSeparator(c)) { 293 ret.append(c); 294 } 295 } 296 297 return ret.toString(); 298 } 299 300 /** 301 * Compare phone numbers a and b, return true if they're identical enough for caller ID purposes. 302 */ 303 public static boolean compare(String a, String b) { 304 // We've used loose comparation at least Eclair, which may change in the future. 305 306 return compare(a, b, false); 307 } 308 309 /** 310 * Compare phone numbers a and b, and return true if they're identical 311 * enough for caller ID purposes. Checks a resource to determine whether 312 * to use a strict or loose comparison algorithm. 313 */ 314 public static boolean compare(Context context, String a, String b) { 315 boolean useStrict = context.getResources().getBoolean( 316 com.android.internal.R.bool.config_use_strict_phone_number_comparation); 317 return compare(a, b, useStrict); 318 } 319 320 /** 321 * @hide only for testing. 322 */ 323 public static boolean compare(String a, String b, boolean useStrictComparation) { 324 return (useStrictComparation ? compareStrictly(a, b) : compareLoosely(a, b)); 325 } 326 327 /** 328 * Compare phone numbers a and b, return true if they're identical 329 * enough for caller ID purposes. 330 * 331 * - Compares from right to left 332 * - requires MIN_MATCH (7) characters to match 333 * - handles common trunk prefixes and international prefixes 334 * (basically, everything except the Russian trunk prefix) 335 * 336 * Note that this method does not return false even when the two phone numbers 337 * are not exactly same; rather; we can call this method "similar()", not "equals()". 338 * 339 * @hide 340 */ 341 public static boolean 342 compareLoosely(String a, String b) { 343 int ia, ib; 344 int matched; 345 346 if (a == null || b == null) return a == b; 347 348 if (a.length() == 0 || b.length() == 0) { 349 return false; 350 } 351 352 ia = indexOfLastNetworkChar (a); 353 ib = indexOfLastNetworkChar (b); 354 matched = 0; 355 356 while (ia >= 0 && ib >=0) { 357 char ca, cb; 358 boolean skipCmp = false; 359 360 ca = a.charAt(ia); 361 362 if (!isDialable(ca)) { 363 ia--; 364 skipCmp = true; 365 } 366 367 cb = b.charAt(ib); 368 369 if (!isDialable(cb)) { 370 ib--; 371 skipCmp = true; 372 } 373 374 if (!skipCmp) { 375 if (cb != ca && ca != WILD && cb != WILD) { 376 break; 377 } 378 ia--; ib--; matched++; 379 } 380 } 381 382 if (matched < MIN_MATCH) { 383 int aLen = a.length(); 384 385 // if the input strings match, but their lengths < MIN_MATCH, 386 // treat them as equal. 387 if (aLen == b.length() && aLen == matched) { 388 return true; 389 } 390 return false; 391 } 392 393 // At least one string has matched completely; 394 if (matched >= MIN_MATCH && (ia < 0 || ib < 0)) { 395 return true; 396 } 397 398 /* 399 * Now, what remains must be one of the following for a 400 * match: 401 * 402 * - a '+' on one and a '00' or a '011' on the other 403 * - a '0' on one and a (+,00)<country code> on the other 404 * (for this, a '0' and a '00' prefix would have succeeded above) 405 */ 406 407 if (matchIntlPrefix(a, ia + 1) 408 && matchIntlPrefix (b, ib +1) 409 ) { 410 return true; 411 } 412 413 if (matchTrunkPrefix(a, ia + 1) 414 && matchIntlPrefixAndCC(b, ib +1) 415 ) { 416 return true; 417 } 418 419 if (matchTrunkPrefix(b, ib + 1) 420 && matchIntlPrefixAndCC(a, ia +1) 421 ) { 422 return true; 423 } 424 425 return false; 426 } 427 428 /** 429 * @hide 430 */ 431 public static boolean 432 compareStrictly(String a, String b) { 433 return compareStrictly(a, b, true); 434 } 435 436 /** 437 * @hide 438 */ 439 public static boolean 440 compareStrictly(String a, String b, boolean acceptInvalidCCCPrefix) { 441 if (a == null || b == null) { 442 return a == b; 443 } else if (a.length() == 0 && b.length() == 0) { 444 return false; 445 } 446 447 int forwardIndexA = 0; 448 int forwardIndexB = 0; 449 450 CountryCallingCodeAndNewIndex cccA = 451 tryGetCountryCallingCodeAndNewIndex(a, acceptInvalidCCCPrefix); 452 CountryCallingCodeAndNewIndex cccB = 453 tryGetCountryCallingCodeAndNewIndex(b, acceptInvalidCCCPrefix); 454 boolean bothHasCountryCallingCode = false; 455 boolean okToIgnorePrefix = true; 456 boolean trunkPrefixIsOmittedA = false; 457 boolean trunkPrefixIsOmittedB = false; 458 if (cccA != null && cccB != null) { 459 if (cccA.countryCallingCode != cccB.countryCallingCode) { 460 // Different Country Calling Code. Must be different phone number. 461 return false; 462 } 463 // When both have ccc, do not ignore trunk prefix. Without this, 464 // "+81123123" becomes same as "+810123123" (+81 == Japan) 465 okToIgnorePrefix = false; 466 bothHasCountryCallingCode = true; 467 forwardIndexA = cccA.newIndex; 468 forwardIndexB = cccB.newIndex; 469 } else if (cccA == null && cccB == null) { 470 // When both do not have ccc, do not ignore trunk prefix. Without this, 471 // "123123" becomes same as "0123123" 472 okToIgnorePrefix = false; 473 } else { 474 if (cccA != null) { 475 forwardIndexA = cccA.newIndex; 476 } else { 477 int tmp = tryGetTrunkPrefixOmittedIndex(b, 0); 478 if (tmp >= 0) { 479 forwardIndexA = tmp; 480 trunkPrefixIsOmittedA = true; 481 } 482 } 483 if (cccB != null) { 484 forwardIndexB = cccB.newIndex; 485 } else { 486 int tmp = tryGetTrunkPrefixOmittedIndex(b, 0); 487 if (tmp >= 0) { 488 forwardIndexB = tmp; 489 trunkPrefixIsOmittedB = true; 490 } 491 } 492 } 493 494 int backwardIndexA = a.length() - 1; 495 int backwardIndexB = b.length() - 1; 496 while (backwardIndexA >= forwardIndexA && backwardIndexB >= forwardIndexB) { 497 boolean skip_compare = false; 498 final char chA = a.charAt(backwardIndexA); 499 final char chB = b.charAt(backwardIndexB); 500 if (isSeparator(chA)) { 501 backwardIndexA--; 502 skip_compare = true; 503 } 504 if (isSeparator(chB)) { 505 backwardIndexB--; 506 skip_compare = true; 507 } 508 509 if (!skip_compare) { 510 if (chA != chB) { 511 return false; 512 } 513 backwardIndexA--; 514 backwardIndexB--; 515 } 516 } 517 518 if (okToIgnorePrefix) { 519 if ((trunkPrefixIsOmittedA && forwardIndexA <= backwardIndexA) || 520 !checkPrefixIsIgnorable(a, forwardIndexA, backwardIndexA)) { 521 if (acceptInvalidCCCPrefix) { 522 // Maybe the code handling the special case for Thailand makes the 523 // result garbled, so disable the code and try again. 524 // e.g. "16610001234" must equal to "6610001234", but with 525 // Thailand-case handling code, they become equal to each other. 526 // 527 // Note: we select simplicity rather than adding some complicated 528 // logic here for performance(like "checking whether remaining 529 // numbers are just 66 or not"), assuming inputs are small 530 // enough. 531 return compare(a, b, false); 532 } else { 533 return false; 534 } 535 } 536 if ((trunkPrefixIsOmittedB && forwardIndexB <= backwardIndexB) || 537 !checkPrefixIsIgnorable(b, forwardIndexA, backwardIndexB)) { 538 if (acceptInvalidCCCPrefix) { 539 return compare(a, b, false); 540 } else { 541 return false; 542 } 543 } 544 } else { 545 // In the US, 1-650-555-1234 must be equal to 650-555-1234, 546 // while 090-1234-1234 must not be equalt to 90-1234-1234 in Japan. 547 // This request exists just in US (with 1 trunk (NDD) prefix). 548 // In addition, "011 11 7005554141" must not equal to "+17005554141", 549 // while "011 1 7005554141" must equal to "+17005554141" 550 // 551 // In this comparison, we ignore the prefix '1' just once, when 552 // - at least either does not have CCC, or 553 // - the remaining non-separator number is 1 554 boolean maybeNamp = !bothHasCountryCallingCode; 555 while (backwardIndexA >= forwardIndexA) { 556 final char chA = a.charAt(backwardIndexA); 557 if (isDialable(chA)) { 558 if (maybeNamp && tryGetISODigit(chA) == 1) { 559 maybeNamp = false; 560 } else { 561 return false; 562 } 563 } 564 backwardIndexA--; 565 } 566 while (backwardIndexB >= forwardIndexB) { 567 final char chB = b.charAt(backwardIndexB); 568 if (isDialable(chB)) { 569 if (maybeNamp && tryGetISODigit(chB) == 1) { 570 maybeNamp = false; 571 } else { 572 return false; 573 } 574 } 575 backwardIndexB--; 576 } 577 } 578 579 return true; 580 } 581 582 /** 583 * Returns the rightmost MIN_MATCH (5) characters in the network portion 584 * in *reversed* order 585 * 586 * This can be used to do a database lookup against the column 587 * that stores getStrippedReversed() 588 * 589 * Returns null if phoneNumber == null 590 */ 591 public static String 592 toCallerIDMinMatch(String phoneNumber) { 593 String np = extractNetworkPortion(phoneNumber); 594 return internalGetStrippedReversed(np, MIN_MATCH); 595 } 596 597 /** 598 * Returns the network portion reversed. 599 * This string is intended to go into an index column for a 600 * database lookup. 601 * 602 * Returns null if phoneNumber == null 603 */ 604 public static String 605 getStrippedReversed(String phoneNumber) { 606 String np = extractNetworkPortion(phoneNumber); 607 608 if (np == null) return null; 609 610 return internalGetStrippedReversed(np, np.length()); 611 } 612 613 /** 614 * Returns the last numDigits of the reversed phone number 615 * Returns null if np == null 616 */ 617 private static String 618 internalGetStrippedReversed(String np, int numDigits) { 619 if (np == null) return null; 620 621 StringBuilder ret = new StringBuilder(numDigits); 622 int length = np.length(); 623 624 for (int i = length - 1, s = length 625 ; i >= 0 && (s - i) <= numDigits ; i-- 626 ) { 627 char c = np.charAt(i); 628 629 ret.append(c); 630 } 631 632 return ret.toString(); 633 } 634 635 /** 636 * Basically: makes sure there's a + in front of a 637 * TOA_International number 638 * 639 * Returns null if s == null 640 */ 641 public static String 642 stringFromStringAndTOA(String s, int TOA) { 643 if (s == null) return null; 644 645 if (TOA == TOA_International && s.length() > 0 && s.charAt(0) != '+') { 646 return "+" + s; 647 } 648 649 return s; 650 } 651 652 /** 653 * Returns the TOA for the given dial string 654 * Basically, returns TOA_International if there's a + prefix 655 */ 656 657 public static int 658 toaFromString(String s) { 659 if (s != null && s.length() > 0 && s.charAt(0) == '+') { 660 return TOA_International; 661 } 662 663 return TOA_Unknown; 664 } 665 666 /** 667 * 3GPP TS 24.008 10.5.4.7 668 * Called Party BCD Number 669 * 670 * See Also TS 51.011 10.5.1 "dialing number/ssc string" 671 * and TS 11.11 "10.3.1 EF adn (Abbreviated dialing numbers)" 672 * 673 * @param bytes the data buffer 674 * @param offset should point to the TOA (aka. TON/NPI) octet after the length byte 675 * @param length is the number of bytes including TOA byte 676 * and must be at least 2 677 * 678 * @return partial string on invalid decode 679 * 680 * FIXME(mkf) support alphanumeric address type 681 * currently implemented in SMSMessage.getAddress() 682 */ 683 public static String 684 calledPartyBCDToString (byte[] bytes, int offset, int length) { 685 boolean prependPlus = false; 686 StringBuilder ret = new StringBuilder(1 + length * 2); 687 688 if (length < 2) { 689 return ""; 690 } 691 692 //Only TON field should be taken in concideration 693 if ((bytes[offset] & 0xf0) == (TOA_International & 0xf0)) { 694 prependPlus = true; 695 } 696 697 internalCalledPartyBCDFragmentToString( 698 ret, bytes, offset + 1, length - 1); 699 700 if (prependPlus && ret.length() == 0) { 701 // If the only thing there is a prepended plus, return "" 702 return ""; 703 } 704 705 if (prependPlus) { 706 // This is an "international number" and should have 707 // a plus prepended to the dialing number. But there 708 // can also be Gsm MMI codes as defined in TS 22.030 6.5.2 709 // so we need to handle those also. 710 // 711 // http://web.telia.com/~u47904776/gsmkode.htm is a 712 // has a nice list of some of these GSM codes. 713 // 714 // Examples are: 715 // **21*+886988171479# 716 // **21*8311234567# 717 // *21# 718 // #21# 719 // *#21# 720 // *31#+11234567890 721 // #31#+18311234567 722 // #31#8311234567 723 // 18311234567 724 // +18311234567# 725 // +18311234567 726 // Odd ball cases that some phones handled 727 // where there is no dialing number so they 728 // append the "+" 729 // *21#+ 730 // **21#+ 731 String retString = ret.toString(); 732 Pattern p = Pattern.compile("(^[#*])(.*)([#*])(.*)(#)$"); 733 Matcher m = p.matcher(retString); 734 if (m.matches()) { 735 if ("".equals(m.group(2))) { 736 // Started with two [#*] ends with # 737 // So no dialing number and we'll just 738 // append a +, this handles **21#+ 739 ret = new StringBuilder(); 740 ret.append(m.group(1)); 741 ret.append(m.group(3)); 742 ret.append(m.group(4)); 743 ret.append(m.group(5)); 744 ret.append("+"); 745 } else { 746 // Starts with [#*] and ends with # 747 // Assume group 4 is a dialing number 748 // such as *21*+1234554# 749 ret = new StringBuilder(); 750 ret.append(m.group(1)); 751 ret.append(m.group(2)); 752 ret.append(m.group(3)); 753 ret.append("+"); 754 ret.append(m.group(4)); 755 ret.append(m.group(5)); 756 } 757 } else { 758 p = Pattern.compile("(^[#*])(.*)([#*])(.*)"); 759 m = p.matcher(retString); 760 if (m.matches()) { 761 // Starts with [#*] and only one other [#*] 762 // Assume the data after last [#*] is dialing 763 // number (i.e. group 4) such as *31#+11234567890. 764 // This also includes the odd ball *21#+ 765 ret = new StringBuilder(); 766 ret.append(m.group(1)); 767 ret.append(m.group(2)); 768 ret.append(m.group(3)); 769 ret.append("+"); 770 ret.append(m.group(4)); 771 } else { 772 // Does NOT start with [#*] just prepend '+' 773 ret = new StringBuilder(); 774 ret.append('+'); 775 ret.append(retString); 776 } 777 } 778 } 779 780 return ret.toString(); 781 } 782 783 private static void 784 internalCalledPartyBCDFragmentToString( 785 StringBuilder sb, byte [] bytes, int offset, int length) { 786 for (int i = offset ; i < length + offset ; i++) { 787 byte b; 788 char c; 789 790 c = bcdToChar((byte)(bytes[i] & 0xf)); 791 792 if (c == 0) { 793 return; 794 } 795 sb.append(c); 796 797 // FIXME(mkf) TS 23.040 9.1.2.3 says 798 // "if a mobile receives 1111 in a position prior to 799 // the last semi-octet then processing shall commense with 800 // the next semi-octet and the intervening 801 // semi-octet shall be ignored" 802 // How does this jive with 24,008 10.5.4.7 803 804 b = (byte)((bytes[i] >> 4) & 0xf); 805 806 if (b == 0xf && i + 1 == length + offset) { 807 //ignore final 0xf 808 break; 809 } 810 811 c = bcdToChar(b); 812 if (c == 0) { 813 return; 814 } 815 816 sb.append(c); 817 } 818 819 } 820 821 /** 822 * Like calledPartyBCDToString, but field does not start with a 823 * TOA byte. For example: SIM ADN extension fields 824 */ 825 826 public static String 827 calledPartyBCDFragmentToString(byte [] bytes, int offset, int length) { 828 StringBuilder ret = new StringBuilder(length * 2); 829 830 internalCalledPartyBCDFragmentToString(ret, bytes, offset, length); 831 832 return ret.toString(); 833 } 834 835 /** returns 0 on invalid value */ 836 private static char 837 bcdToChar(byte b) { 838 if (b < 0xa) { 839 return (char)('0' + b); 840 } else switch (b) { 841 case 0xa: return '*'; 842 case 0xb: return '#'; 843 case 0xc: return PAUSE; 844 case 0xd: return WILD; 845 846 default: return 0; 847 } 848 } 849 850 private static int 851 charToBCD(char c) { 852 if (c >= '0' && c <= '9') { 853 return c - '0'; 854 } else if (c == '*') { 855 return 0xa; 856 } else if (c == '#') { 857 return 0xb; 858 } else if (c == PAUSE) { 859 return 0xc; 860 } else if (c == WILD) { 861 return 0xd; 862 } else { 863 throw new RuntimeException ("invalid char for BCD " + c); 864 } 865 } 866 867 /** 868 * Return true iff the network portion of <code>address</code> is, 869 * as far as we can tell on the device, suitable for use as an SMS 870 * destination address. 871 */ 872 public static boolean isWellFormedSmsAddress(String address) { 873 String networkPortion = 874 PhoneNumberUtils.extractNetworkPortion(address); 875 876 return (!(networkPortion.equals("+") 877 || TextUtils.isEmpty(networkPortion))) 878 && isDialable(networkPortion); 879 } 880 881 public static boolean isGlobalPhoneNumber(String phoneNumber) { 882 if (TextUtils.isEmpty(phoneNumber)) { 883 return false; 884 } 885 886 Matcher match = GLOBAL_PHONE_NUMBER_PATTERN.matcher(phoneNumber); 887 return match.matches(); 888 } 889 890 private static boolean isDialable(String address) { 891 for (int i = 0, count = address.length(); i < count; i++) { 892 if (!isDialable(address.charAt(i))) { 893 return false; 894 } 895 } 896 return true; 897 } 898 899 private static boolean isNonSeparator(String address) { 900 for (int i = 0, count = address.length(); i < count; i++) { 901 if (!isNonSeparator(address.charAt(i))) { 902 return false; 903 } 904 } 905 return true; 906 } 907 /** 908 * Note: calls extractNetworkPortion(), so do not use for 909 * SIM EF[ADN] style records 910 * 911 * Returns null if network portion is empty. 912 */ 913 public static byte[] 914 networkPortionToCalledPartyBCD(String s) { 915 String networkPortion = extractNetworkPortion(s); 916 return numberToCalledPartyBCDHelper(networkPortion, false); 917 } 918 919 /** 920 * Same as {@link #networkPortionToCalledPartyBCD}, but includes a 921 * one-byte length prefix. 922 */ 923 public static byte[] 924 networkPortionToCalledPartyBCDWithLength(String s) { 925 String networkPortion = extractNetworkPortion(s); 926 return numberToCalledPartyBCDHelper(networkPortion, true); 927 } 928 929 /** 930 * Convert a dialing number to BCD byte array 931 * 932 * @param number dialing number string 933 * if the dialing number starts with '+', set to internationl TOA 934 * @return BCD byte array 935 */ 936 public static byte[] 937 numberToCalledPartyBCD(String number) { 938 return numberToCalledPartyBCDHelper(number, false); 939 } 940 941 /** 942 * If includeLength is true, prepend a one-byte length value to 943 * the return array. 944 */ 945 private static byte[] 946 numberToCalledPartyBCDHelper(String number, boolean includeLength) { 947 int numberLenReal = number.length(); 948 int numberLenEffective = numberLenReal; 949 boolean hasPlus = number.indexOf('+') != -1; 950 if (hasPlus) numberLenEffective--; 951 952 if (numberLenEffective == 0) return null; 953 954 int resultLen = (numberLenEffective + 1) / 2; // Encoded numbers require only 4 bits each. 955 int extraBytes = 1; // Prepended TOA byte. 956 if (includeLength) extraBytes++; // Optional prepended length byte. 957 resultLen += extraBytes; 958 959 byte[] result = new byte[resultLen]; 960 961 int digitCount = 0; 962 for (int i = 0; i < numberLenReal; i++) { 963 char c = number.charAt(i); 964 if (c == '+') continue; 965 int shift = ((digitCount & 0x01) == 1) ? 4 : 0; 966 result[extraBytes + (digitCount >> 1)] |= (byte)((charToBCD(c) & 0x0F) << shift); 967 digitCount++; 968 } 969 970 // 1-fill any trailing odd nibble/quartet. 971 if ((digitCount & 0x01) == 1) result[extraBytes + (digitCount >> 1)] |= 0xF0; 972 973 int offset = 0; 974 if (includeLength) result[offset++] = (byte)(resultLen - 1); 975 result[offset] = (byte)(hasPlus ? TOA_International : TOA_Unknown); 976 977 return result; 978 } 979 980 //================ Number formatting ========================= 981 982 /** The current locale is unknown, look for a country code or don't format */ 983 public static final int FORMAT_UNKNOWN = 0; 984 /** NANP formatting */ 985 public static final int FORMAT_NANP = 1; 986 /** Japanese formatting */ 987 public static final int FORMAT_JAPAN = 2; 988 989 /** List of country codes for countries that use the NANP */ 990 private static final String[] NANP_COUNTRIES = new String[] { 991 "US", // United States 992 "CA", // Canada 993 "AS", // American Samoa 994 "AI", // Anguilla 995 "AG", // Antigua and Barbuda 996 "BS", // Bahamas 997 "BB", // Barbados 998 "BM", // Bermuda 999 "VG", // British Virgin Islands 1000 "KY", // Cayman Islands 1001 "DM", // Dominica 1002 "DO", // Dominican Republic 1003 "GD", // Grenada 1004 "GU", // Guam 1005 "JM", // Jamaica 1006 "PR", // Puerto Rico 1007 "MS", // Montserrat 1008 "MP", // Northern Mariana Islands 1009 "KN", // Saint Kitts and Nevis 1010 "LC", // Saint Lucia 1011 "VC", // Saint Vincent and the Grenadines 1012 "TT", // Trinidad and Tobago 1013 "TC", // Turks and Caicos Islands 1014 "VI", // U.S. Virgin Islands 1015 }; 1016 1017 /** 1018 * Breaks the given number down and formats it according to the rules 1019 * for the country the number is from. 1020 * 1021 * @param source the phone number to format 1022 * @return a locally acceptable formatting of the input, or the raw input if 1023 * formatting rules aren't known for the number 1024 */ 1025 public static String formatNumber(String source) { 1026 SpannableStringBuilder text = new SpannableStringBuilder(source); 1027 formatNumber(text, getFormatTypeForLocale(Locale.getDefault())); 1028 return text.toString(); 1029 } 1030 1031 /** 1032 * Returns the phone number formatting type for the given locale. 1033 * 1034 * @param locale The locale of interest, usually {@link Locale#getDefault()} 1035 * @return the formatting type for the given locale, or FORMAT_UNKNOWN if the formatting 1036 * rules are not known for the given locale 1037 */ 1038 public static int getFormatTypeForLocale(Locale locale) { 1039 String country = locale.getCountry(); 1040 1041 return getFormatTypeFromCountryCode(country); 1042 } 1043 1044 /** 1045 * Formats a phone number in-place. Currently only supports NANP formatting. 1046 * 1047 * @param text The number to be formatted, will be modified with the formatting 1048 * @param defaultFormattingType The default formatting rules to apply if the number does 1049 * not begin with +<country_code> 1050 */ 1051 public static void formatNumber(Editable text, int defaultFormattingType) { 1052 int formatType = defaultFormattingType; 1053 1054 if (text.length() > 2 && text.charAt(0) == '+') { 1055 if (text.charAt(1) == '1') { 1056 formatType = FORMAT_NANP; 1057 } else if (text.length() >= 3 && text.charAt(1) == '8' 1058 && text.charAt(2) == '1') { 1059 formatType = FORMAT_JAPAN; 1060 } else { 1061 formatType = FORMAT_UNKNOWN; 1062 } 1063 } 1064 1065 switch (formatType) { 1066 case FORMAT_NANP: 1067 formatNanpNumber(text); 1068 return; 1069 case FORMAT_JAPAN: 1070 formatJapaneseNumber(text); 1071 return; 1072 case FORMAT_UNKNOWN: 1073 removeDashes(text); 1074 return; 1075 } 1076 } 1077 1078 private static final int NANP_STATE_DIGIT = 1; 1079 private static final int NANP_STATE_PLUS = 2; 1080 private static final int NANP_STATE_ONE = 3; 1081 private static final int NANP_STATE_DASH = 4; 1082 1083 /** 1084 * Formats a phone number in-place using the NANP formatting rules. Numbers will be formatted 1085 * as: 1086 * 1087 * <p><code> 1088 * xxxxx 1089 * xxx-xxxx 1090 * xxx-xxx-xxxx 1091 * 1-xxx-xxx-xxxx 1092 * +1-xxx-xxx-xxxx 1093 * </code></p> 1094 * 1095 * @param text the number to be formatted, will be modified with the formatting 1096 */ 1097 public static void formatNanpNumber(Editable text) { 1098 int length = text.length(); 1099 if (length > "+1-nnn-nnn-nnnn".length()) { 1100 // The string is too long to be formatted 1101 return; 1102 } else if (length <= 5) { 1103 // The string is either a shortcode or too short to be formatted 1104 return; 1105 } 1106 1107 CharSequence saved = text.subSequence(0, length); 1108 1109 // Strip the dashes first, as we're going to add them back 1110 removeDashes(text); 1111 length = text.length(); 1112 1113 // When scanning the number we record where dashes need to be added, 1114 // if they're non-0 at the end of the scan the dashes will be added in 1115 // the proper places. 1116 int dashPositions[] = new int[3]; 1117 int numDashes = 0; 1118 1119 int state = NANP_STATE_DIGIT; 1120 int numDigits = 0; 1121 for (int i = 0; i < length; i++) { 1122 char c = text.charAt(i); 1123 switch (c) { 1124 case '1': 1125 if (numDigits == 0 || state == NANP_STATE_PLUS) { 1126 state = NANP_STATE_ONE; 1127 break; 1128 } 1129 // fall through 1130 case '2': 1131 case '3': 1132 case '4': 1133 case '5': 1134 case '6': 1135 case '7': 1136 case '8': 1137 case '9': 1138 case '0': 1139 if (state == NANP_STATE_PLUS) { 1140 // Only NANP number supported for now 1141 text.replace(0, length, saved); 1142 return; 1143 } else if (state == NANP_STATE_ONE) { 1144 // Found either +1 or 1, follow it up with a dash 1145 dashPositions[numDashes++] = i; 1146 } else if (state != NANP_STATE_DASH && (numDigits == 3 || numDigits == 6)) { 1147 // Found a digit that should be after a dash that isn't 1148 dashPositions[numDashes++] = i; 1149 } 1150 state = NANP_STATE_DIGIT; 1151 numDigits++; 1152 break; 1153 1154 case '-': 1155 state = NANP_STATE_DASH; 1156 break; 1157 1158 case '+': 1159 if (i == 0) { 1160 // Plus is only allowed as the first character 1161 state = NANP_STATE_PLUS; 1162 break; 1163 } 1164 // Fall through 1165 default: 1166 // Unknown character, bail on formatting 1167 text.replace(0, length, saved); 1168 return; 1169 } 1170 } 1171 1172 if (numDigits == 7) { 1173 // With 7 digits we want xxx-xxxx, not xxx-xxx-x 1174 numDashes--; 1175 } 1176 1177 // Actually put the dashes in place 1178 for (int i = 0; i < numDashes; i++) { 1179 int pos = dashPositions[i]; 1180 text.replace(pos + i, pos + i, "-"); 1181 } 1182 1183 // Remove trailing dashes 1184 int len = text.length(); 1185 while (len > 0) { 1186 if (text.charAt(len - 1) == '-') { 1187 text.delete(len - 1, len); 1188 len--; 1189 } else { 1190 break; 1191 } 1192 } 1193 } 1194 1195 /** 1196 * Formats a phone number in-place using the Japanese formatting rules. 1197 * Numbers will be formatted as: 1198 * 1199 * <p><code> 1200 * 03-xxxx-xxxx 1201 * 090-xxxx-xxxx 1202 * 0120-xxx-xxx 1203 * +81-3-xxxx-xxxx 1204 * +81-90-xxxx-xxxx 1205 * </code></p> 1206 * 1207 * @param text the number to be formatted, will be modified with 1208 * the formatting 1209 */ 1210 public static void formatJapaneseNumber(Editable text) { 1211 JapanesePhoneNumberFormatter.format(text); 1212 } 1213 1214 /** 1215 * Removes all dashes from the number. 1216 * 1217 * @param text the number to clear from dashes 1218 */ 1219 private static void removeDashes(Editable text) { 1220 int p = 0; 1221 while (p < text.length()) { 1222 if (text.charAt(p) == '-') { 1223 text.delete(p, p + 1); 1224 } else { 1225 p++; 1226 } 1227 } 1228 } 1229 1230 // Three and four digit phone numbers for either special services, 1231 // or 3-6 digit addresses from the network (eg carrier-originated SMS messages) should 1232 // not match. 1233 // 1234 // This constant used to be 5, but SMS short codes has increased in length and 1235 // can be easily 6 digits now days. Most countries have SMS short code length between 1236 // 3 to 6 digits. The exceptions are 1237 // 1238 // Australia: Short codes are six or eight digits in length, starting with the prefix "19" 1239 // followed by an additional four or six digits and two. 1240 // Czech Republic: Codes are seven digits in length for MO and five (not billed) or 1241 // eight (billed) for MT direction 1242 // 1243 // see http://en.wikipedia.org/wiki/Short_code#Regional_differences for reference 1244 // 1245 // However, in order to loose match 650-555-1212 and 555-1212, we need to set the min match 1246 // to 7. 1247 static final int MIN_MATCH = 7; 1248 1249 /** 1250 * isEmergencyNumber: checks a given number against the list of 1251 * emergency numbers provided by the RIL and SIM card. 1252 * 1253 * @param number the number to look up. 1254 * @return if the number is in the list of emergency numbers 1255 * listed in the ril / sim, then return true, otherwise false. 1256 */ 1257 public static boolean isEmergencyNumber(String number) { 1258 // If the number passed in is null, just return false: 1259 if (number == null) return false; 1260 1261 // Strip the separators from the number before comparing it 1262 // to the list. 1263 number = extractNetworkPortion(number); 1264 1265 // retrieve the list of emergency numbers 1266 String numbers = SystemProperties.get("ro.ril.ecclist"); 1267 1268 if (!TextUtils.isEmpty(numbers)) { 1269 // searches through the comma-separated list for a match, 1270 // return true if one is found. 1271 for (String emergencyNum : numbers.split(",")) { 1272 if (emergencyNum.equals(number)) { 1273 return true; 1274 } 1275 } 1276 // no matches found against the list! 1277 return false; 1278 } 1279 1280 //no ecclist system property, so use our own list. 1281 return (number.equals("112") || number.equals("911")); 1282 } 1283 1284 /** 1285 * isVoiceMailNumber: checks a given number against the voicemail 1286 * number provided by the RIL and SIM card. The caller must have 1287 * the READ_PHONE_STATE credential. 1288 * 1289 * @param number the number to look up. 1290 * @return true if the number is in the list of voicemail. False 1291 * otherwise, including if the caller does not have the permission 1292 * to read the VM number. 1293 * @hide TODO: pending API Council approval 1294 */ 1295 public static boolean isVoiceMailNumber(String number) { 1296 String vmNumber; 1297 1298 try { 1299 vmNumber = TelephonyManager.getDefault().getVoiceMailNumber(); 1300 } catch (SecurityException ex) { 1301 return false; 1302 } 1303 1304 // Strip the separators from the number before comparing it 1305 // to the list. 1306 number = extractNetworkPortion(number); 1307 1308 // compare tolerates null so we need to make sure that we 1309 // don't return true when both are null. 1310 return !TextUtils.isEmpty(number) && compare(number, vmNumber); 1311 } 1312 1313 /** 1314 * Translates any alphabetic letters (i.e. [A-Za-z]) in the 1315 * specified phone number into the equivalent numeric digits, 1316 * according to the phone keypad letter mapping described in 1317 * ITU E.161 and ISO/IEC 9995-8. 1318 * 1319 * @return the input string, with alpha letters converted to numeric 1320 * digits using the phone keypad letter mapping. For example, 1321 * an input of "1-800-GOOG-411" will return "1-800-4664-411". 1322 */ 1323 public static String convertKeypadLettersToDigits(String input) { 1324 if (input == null) { 1325 return input; 1326 } 1327 int len = input.length(); 1328 if (len == 0) { 1329 return input; 1330 } 1331 1332 char[] out = input.toCharArray(); 1333 1334 for (int i = 0; i < len; i++) { 1335 char c = out[i]; 1336 // If this char isn't in KEYPAD_MAP at all, just leave it alone. 1337 out[i] = (char) KEYPAD_MAP.get(c, c); 1338 } 1339 1340 return new String(out); 1341 } 1342 1343 /** 1344 * The phone keypad letter mapping (see ITU E.161 or ISO/IEC 9995-8.) 1345 * TODO: This should come from a resource. 1346 */ 1347 private static final SparseIntArray KEYPAD_MAP = new SparseIntArray(); 1348 static { 1349 KEYPAD_MAP.put('a', '2'); KEYPAD_MAP.put('b', '2'); KEYPAD_MAP.put('c', '2'); 1350 KEYPAD_MAP.put('A', '2'); KEYPAD_MAP.put('B', '2'); KEYPAD_MAP.put('C', '2'); 1351 1352 KEYPAD_MAP.put('d', '3'); KEYPAD_MAP.put('e', '3'); KEYPAD_MAP.put('f', '3'); 1353 KEYPAD_MAP.put('D', '3'); KEYPAD_MAP.put('E', '3'); KEYPAD_MAP.put('F', '3'); 1354 1355 KEYPAD_MAP.put('g', '4'); KEYPAD_MAP.put('h', '4'); KEYPAD_MAP.put('i', '4'); 1356 KEYPAD_MAP.put('G', '4'); KEYPAD_MAP.put('H', '4'); KEYPAD_MAP.put('I', '4'); 1357 1358 KEYPAD_MAP.put('j', '5'); KEYPAD_MAP.put('k', '5'); KEYPAD_MAP.put('l', '5'); 1359 KEYPAD_MAP.put('J', '5'); KEYPAD_MAP.put('K', '5'); KEYPAD_MAP.put('L', '5'); 1360 1361 KEYPAD_MAP.put('m', '6'); KEYPAD_MAP.put('n', '6'); KEYPAD_MAP.put('o', '6'); 1362 KEYPAD_MAP.put('M', '6'); KEYPAD_MAP.put('N', '6'); KEYPAD_MAP.put('O', '6'); 1363 1364 KEYPAD_MAP.put('p', '7'); KEYPAD_MAP.put('q', '7'); KEYPAD_MAP.put('r', '7'); KEYPAD_MAP.put('s', '7'); 1365 KEYPAD_MAP.put('P', '7'); KEYPAD_MAP.put('Q', '7'); KEYPAD_MAP.put('R', '7'); KEYPAD_MAP.put('S', '7'); 1366 1367 KEYPAD_MAP.put('t', '8'); KEYPAD_MAP.put('u', '8'); KEYPAD_MAP.put('v', '8'); 1368 KEYPAD_MAP.put('T', '8'); KEYPAD_MAP.put('U', '8'); KEYPAD_MAP.put('V', '8'); 1369 1370 KEYPAD_MAP.put('w', '9'); KEYPAD_MAP.put('x', '9'); KEYPAD_MAP.put('y', '9'); KEYPAD_MAP.put('z', '9'); 1371 KEYPAD_MAP.put('W', '9'); KEYPAD_MAP.put('X', '9'); KEYPAD_MAP.put('Y', '9'); KEYPAD_MAP.put('Z', '9'); 1372 } 1373 1374 //================ Plus Code formatting ========================= 1375 private static final char PLUS_SIGN_CHAR = '+'; 1376 private static final String PLUS_SIGN_STRING = "+"; 1377 private static final String NANP_IDP_STRING = "011"; 1378 private static final int NANP_LENGTH = 10; 1379 1380 /** 1381 * This function checks if there is a plus sign (+) in the passed-in dialing number. 1382 * If there is, it processes the plus sign based on the default telephone 1383 * numbering plan of the system when the phone is activated and the current 1384 * telephone numbering plan of the system that the phone is camped on. 1385 * Currently, we only support the case that the default and current telephone 1386 * numbering plans are North American Numbering Plan(NANP). 1387 * 1388 * The passed-in dialStr should only contain the valid format as described below, 1389 * 1) the 1st character in the dialStr should be one of the really dialable 1390 * characters listed below 1391 * ISO-LATIN characters 0-9, *, # , + 1392 * 2) the dialStr should already strip out the separator characters, 1393 * every character in the dialStr should be one of the non separator characters 1394 * listed below 1395 * ISO-LATIN characters 0-9, *, # , +, WILD, WAIT, PAUSE 1396 * 1397 * Otherwise, this function returns the dial string passed in 1398 * 1399 * @param dialStr the original dial string 1400 * @return the converted dial string if the current/default countries belong to NANP, 1401 * and if there is the "+" in the original dial string. Otherwise, the original dial 1402 * string returns. 1403 * 1404 * This API is for CDMA only 1405 * 1406 * @hide TODO: pending API Council approval 1407 */ 1408 public static String cdmaCheckAndProcessPlusCode(String dialStr) { 1409 if (!TextUtils.isEmpty(dialStr)) { 1410 if (isReallyDialable(dialStr.charAt(0)) && 1411 isNonSeparator(dialStr)) { 1412 String currIso = SystemProperties.get(PROPERTY_OPERATOR_ISO_COUNTRY, ""); 1413 String defaultIso = SystemProperties.get(PROPERTY_ICC_OPERATOR_ISO_COUNTRY, ""); 1414 if (!TextUtils.isEmpty(currIso) && !TextUtils.isEmpty(defaultIso)) { 1415 return cdmaCheckAndProcessPlusCodeByNumberFormat(dialStr, 1416 getFormatTypeFromCountryCode(currIso), 1417 getFormatTypeFromCountryCode(defaultIso)); 1418 } 1419 } 1420 } 1421 return dialStr; 1422 } 1423 1424 /** 1425 * This function should be called from checkAndProcessPlusCode only 1426 * And it is used for test purpose also. 1427 * 1428 * It checks the dial string by looping through the network portion, 1429 * post dial portion 1, post dial porting 2, etc. If there is any 1430 * plus sign, then process the plus sign. 1431 * Currently, this function supports the plus sign conversion within NANP only. 1432 * Specifically, it handles the plus sign in the following ways: 1433 * 1)+1NANP,remove +, e.g. 1434 * +18475797000 is converted to 18475797000, 1435 * 2)+NANP or +non-NANP Numbers,replace + with the current NANP IDP, e.g, 1436 * +8475797000 is converted to 0118475797000, 1437 * +11875767800 is converted to 01111875767800 1438 * 3)+1NANP in post dial string(s), e.g. 1439 * 8475797000;+18475231753 is converted to 8475797000;18475231753 1440 * 1441 * 1442 * @param dialStr the original dial string 1443 * @param currFormat the numbering system of the current country that the phone is camped on 1444 * @param defaultFormat the numbering system of the country that the phone is activated on 1445 * @return the converted dial string if the current/default countries belong to NANP, 1446 * and if there is the "+" in the original dial string. Otherwise, the original dial 1447 * string returns. 1448 * 1449 * @hide 1450 */ 1451 public static String 1452 cdmaCheckAndProcessPlusCodeByNumberFormat(String dialStr,int currFormat,int defaultFormt) { 1453 String retStr = dialStr; 1454 1455 // Checks if the plus sign character is in the passed-in dial string 1456 if (dialStr != null && 1457 dialStr.lastIndexOf(PLUS_SIGN_STRING) != -1) { 1458 // Format the string based on the rules for the country the number is from, 1459 // and the current country the phone is camped on. 1460 if ((currFormat == defaultFormt) && (currFormat == FORMAT_NANP)) { 1461 // Handle case where default and current telephone numbering plans are NANP. 1462 String postDialStr = null; 1463 String tempDialStr = dialStr; 1464 1465 // Sets the retStr to null since the conversion will be performed below. 1466 retStr = null; 1467 if (DBG) log("checkAndProcessPlusCode,dialStr=" + dialStr); 1468 // This routine is to process the plus sign in the dial string by loop through 1469 // the network portion, post dial portion 1, post dial portion 2... etc. if 1470 // applied 1471 do { 1472 String networkDialStr; 1473 networkDialStr = extractNetworkPortion(tempDialStr); 1474 // Handles the conversion within NANP 1475 networkDialStr = processPlusCodeWithinNanp(networkDialStr); 1476 1477 // Concatenates the string that is converted from network portion 1478 if (!TextUtils.isEmpty(networkDialStr)) { 1479 if (retStr == null) { 1480 retStr = networkDialStr; 1481 } else { 1482 retStr = retStr.concat(networkDialStr); 1483 } 1484 } else { 1485 // This should never happen since we checked the if dialStr is null 1486 // and if it contains the plus sign in the beginning of this function. 1487 // The plus sign is part of the network portion. 1488 Log.e("checkAndProcessPlusCode: null newDialStr", networkDialStr); 1489 return dialStr; 1490 } 1491 postDialStr = extractPostDialPortion(tempDialStr); 1492 if (!TextUtils.isEmpty(postDialStr)) { 1493 int dialableIndex = findDialableIndexFromPostDialStr(postDialStr); 1494 1495 // dialableIndex should always be greater than 0 1496 if (dialableIndex >= 1) { 1497 retStr = appendPwCharBackToOrigDialStr(dialableIndex, 1498 retStr,postDialStr); 1499 // Skips the P/W character, extracts the dialable portion 1500 tempDialStr = postDialStr.substring(dialableIndex); 1501 } else { 1502 // Non-dialable character such as P/W should not be at the end of 1503 // the dial string after P/W processing in CdmaConnection.java 1504 // Set the postDialStr to "" to break out of the loop 1505 if (dialableIndex < 0) { 1506 postDialStr = ""; 1507 } 1508 Log.e("wrong postDialStr=", postDialStr); 1509 } 1510 } 1511 if (DBG) log("checkAndProcessPlusCode,postDialStr=" + postDialStr); 1512 } while (!TextUtils.isEmpty(postDialStr) && !TextUtils.isEmpty(tempDialStr)); 1513 } else { 1514 // TODO: Support NANP international conversion and other telephone numbering plans. 1515 // Currently the phone is never used in non-NANP system, so return the original 1516 // dial string. 1517 Log.e("checkAndProcessPlusCode:non-NANP not supported", dialStr); 1518 } 1519 } 1520 return retStr; 1521 } 1522 1523 // This function gets the default international dialing prefix 1524 private static String getDefaultIdp( ) { 1525 String ps = null; 1526 SystemProperties.get(PROPERTY_IDP_STRING, ps); 1527 if (TextUtils.isEmpty(ps)) { 1528 ps = NANP_IDP_STRING; 1529 } 1530 return ps; 1531 } 1532 1533 private static boolean isTwoToNine (char c) { 1534 if (c >= '2' && c <= '9') { 1535 return true; 1536 } else { 1537 return false; 1538 } 1539 } 1540 1541 private static int getFormatTypeFromCountryCode (String country) { 1542 // Check for the NANP countries 1543 int length = NANP_COUNTRIES.length; 1544 for (int i = 0; i < length; i++) { 1545 if (NANP_COUNTRIES[i].compareToIgnoreCase(country) == 0) { 1546 return FORMAT_NANP; 1547 } 1548 } 1549 if ("jp".compareToIgnoreCase(country) == 0) { 1550 return FORMAT_JAPAN; 1551 } 1552 return FORMAT_UNKNOWN; 1553 } 1554 1555 /** 1556 * This function checks if the passed in string conforms to the NANP format 1557 * i.e. NXX-NXX-XXXX, N is any digit 2-9 and X is any digit 0-9 1558 */ 1559 private static boolean isNanp (String dialStr) { 1560 boolean retVal = false; 1561 if (dialStr != null) { 1562 if (dialStr.length() == NANP_LENGTH) { 1563 if (isTwoToNine(dialStr.charAt(0)) && 1564 isTwoToNine(dialStr.charAt(3))) { 1565 retVal = true; 1566 for (int i=1; i<NANP_LENGTH; i++ ) { 1567 char c=dialStr.charAt(i); 1568 if (!PhoneNumberUtils.isISODigit(c)) { 1569 retVal = false; 1570 break; 1571 } 1572 } 1573 } 1574 } 1575 } else { 1576 Log.e("isNanp: null dialStr passed in", dialStr); 1577 } 1578 return retVal; 1579 } 1580 1581 /** 1582 * This function checks if the passed in string conforms to 1-NANP format 1583 */ 1584 private static boolean isOneNanp(String dialStr) { 1585 boolean retVal = false; 1586 if (dialStr != null) { 1587 String newDialStr = dialStr.substring(1); 1588 if ((dialStr.charAt(0) == '1') && isNanp(newDialStr)) { 1589 retVal = true; 1590 } 1591 } else { 1592 Log.e("isOneNanp: null dialStr passed in", dialStr); 1593 } 1594 return retVal; 1595 } 1596 1597 /** 1598 * This function handles the plus code conversion within NANP CDMA network 1599 * If the number format is 1600 * 1)+1NANP,remove +, 1601 * 2)other than +1NANP, any + numbers,replace + with the current IDP 1602 */ 1603 private static String processPlusCodeWithinNanp(String networkDialStr) { 1604 String retStr = networkDialStr; 1605 1606 if (DBG) log("processPlusCodeWithinNanp,networkDialStr=" + networkDialStr); 1607 // If there is a plus sign at the beginning of the dial string, 1608 // Convert the plus sign to the default IDP since it's an international number 1609 if (networkDialStr != null & 1610 networkDialStr.charAt(0) == PLUS_SIGN_CHAR && 1611 networkDialStr.length() > 1) { 1612 String newStr = networkDialStr.substring(1); 1613 if (isOneNanp(newStr)) { 1614 // Remove the leading plus sign 1615 retStr = newStr; 1616 } else { 1617 String idpStr = getDefaultIdp(); 1618 // Replaces the plus sign with the default IDP 1619 retStr = networkDialStr.replaceFirst("[+]", idpStr); 1620 } 1621 } 1622 if (DBG) log("processPlusCodeWithinNanp,retStr=" + retStr); 1623 return retStr; 1624 } 1625 1626 // This function finds the index of the dialable character(s) 1627 // in the post dial string 1628 private static int findDialableIndexFromPostDialStr(String postDialStr) { 1629 for (int index = 0;index < postDialStr.length();index++) { 1630 char c = postDialStr.charAt(index); 1631 if (isReallyDialable(c)) { 1632 return index; 1633 } 1634 } 1635 return -1; 1636 } 1637 1638 // This function appends the non-diablable P/W character to the original 1639 // dial string based on the dialable index passed in 1640 private static String 1641 appendPwCharBackToOrigDialStr(int dialableIndex,String origStr, String dialStr) { 1642 String retStr; 1643 1644 // There is only 1 P/W character before the dialable characters 1645 if (dialableIndex == 1) { 1646 StringBuilder ret = new StringBuilder(origStr); 1647 ret = ret.append(dialStr.charAt(0)); 1648 retStr = ret.toString(); 1649 } else { 1650 // It means more than 1 P/W characters in the post dial string, 1651 // appends to retStr 1652 String nonDigitStr = dialStr.substring(0,dialableIndex); 1653 retStr = origStr.concat(nonDigitStr); 1654 } 1655 return retStr; 1656 } 1657 1658 //===== Begining of utility methods used in compareLoosely() ===== 1659 1660 /** 1661 * Phone numbers are stored in "lookup" form in the database 1662 * as reversed strings to allow for caller ID lookup 1663 * 1664 * This method takes a phone number and makes a valid SQL "LIKE" 1665 * string that will match the lookup form 1666 * 1667 */ 1668 /** all of a up to len must be an international prefix or 1669 * separators/non-dialing digits 1670 */ 1671 private static boolean 1672 matchIntlPrefix(String a, int len) { 1673 /* '([^0-9*#+pwn]\+[^0-9*#+pwn] | [^0-9*#+pwn]0(0|11)[^0-9*#+pwn] )$' */ 1674 /* 0 1 2 3 45 */ 1675 1676 int state = 0; 1677 for (int i = 0 ; i < len ; i++) { 1678 char c = a.charAt(i); 1679 1680 switch (state) { 1681 case 0: 1682 if (c == '+') state = 1; 1683 else if (c == '0') state = 2; 1684 else if (isNonSeparator(c)) return false; 1685 break; 1686 1687 case 2: 1688 if (c == '0') state = 3; 1689 else if (c == '1') state = 4; 1690 else if (isNonSeparator(c)) return false; 1691 break; 1692 1693 case 4: 1694 if (c == '1') state = 5; 1695 else if (isNonSeparator(c)) return false; 1696 break; 1697 1698 default: 1699 if (isNonSeparator(c)) return false; 1700 break; 1701 1702 } 1703 } 1704 1705 return state == 1 || state == 3 || state == 5; 1706 } 1707 1708 /** all of 'a' up to len must be a (+|00|011)country code) 1709 * We're fast and loose with the country code. Any \d{1,3} matches */ 1710 private static boolean 1711 matchIntlPrefixAndCC(String a, int len) { 1712 /* [^0-9*#+pwn]*(\+|0(0|11)\d\d?\d? [^0-9*#+pwn] $ */ 1713 /* 0 1 2 3 45 6 7 8 */ 1714 1715 int state = 0; 1716 for (int i = 0 ; i < len ; i++ ) { 1717 char c = a.charAt(i); 1718 1719 switch (state) { 1720 case 0: 1721 if (c == '+') state = 1; 1722 else if (c == '0') state = 2; 1723 else if (isNonSeparator(c)) return false; 1724 break; 1725 1726 case 2: 1727 if (c == '0') state = 3; 1728 else if (c == '1') state = 4; 1729 else if (isNonSeparator(c)) return false; 1730 break; 1731 1732 case 4: 1733 if (c == '1') state = 5; 1734 else if (isNonSeparator(c)) return false; 1735 break; 1736 1737 case 1: 1738 case 3: 1739 case 5: 1740 if (isISODigit(c)) state = 6; 1741 else if (isNonSeparator(c)) return false; 1742 break; 1743 1744 case 6: 1745 case 7: 1746 if (isISODigit(c)) state++; 1747 else if (isNonSeparator(c)) return false; 1748 break; 1749 1750 default: 1751 if (isNonSeparator(c)) return false; 1752 } 1753 } 1754 1755 return state == 6 || state == 7 || state == 8; 1756 } 1757 1758 /** all of 'a' up to len must match non-US trunk prefix ('0') */ 1759 private static boolean 1760 matchTrunkPrefix(String a, int len) { 1761 boolean found; 1762 1763 found = false; 1764 1765 for (int i = 0 ; i < len ; i++) { 1766 char c = a.charAt(i); 1767 1768 if (c == '0' && !found) { 1769 found = true; 1770 } else if (isNonSeparator(c)) { 1771 return false; 1772 } 1773 } 1774 1775 return found; 1776 } 1777 1778 //===== End of utility methods used only in compareLoosely() ===== 1779 1780 //===== Beggining of utility methods used only in compareStrictly() ==== 1781 1782 /* 1783 * If true, the number is country calling code. 1784 */ 1785 private static final boolean COUNTLY_CALLING_CALL[] = { 1786 true, true, false, false, false, false, false, true, false, false, 1787 false, false, false, false, false, false, false, false, false, false, 1788 true, false, false, false, false, false, false, true, true, false, 1789 true, true, true, true, true, false, true, false, false, true, 1790 true, false, false, true, true, true, true, true, true, true, 1791 false, true, true, true, true, true, true, true, true, false, 1792 true, true, true, true, true, true, true, false, false, false, 1793 false, false, false, false, false, false, false, false, false, false, 1794 false, true, true, true, true, false, true, false, false, true, 1795 true, true, true, true, true, true, false, false, true, false, 1796 }; 1797 private static final int CCC_LENGTH = COUNTLY_CALLING_CALL.length; 1798 1799 /** 1800 * @return true when input is valid Country Calling Code. 1801 */ 1802 private static boolean isCountryCallingCode(int countryCallingCodeCandidate) { 1803 return countryCallingCodeCandidate > 0 && countryCallingCodeCandidate < CCC_LENGTH && 1804 COUNTLY_CALLING_CALL[countryCallingCodeCandidate]; 1805 } 1806 1807 /** 1808 * Returns interger corresponding to the input if input "ch" is 1809 * ISO-LATIN characters 0-9. 1810 * Returns -1 otherwise 1811 */ 1812 private static int tryGetISODigit(char ch) { 1813 if ('0' <= ch && ch <= '9') { 1814 return ch - '0'; 1815 } else { 1816 return -1; 1817 } 1818 } 1819 1820 private static class CountryCallingCodeAndNewIndex { 1821 public final int countryCallingCode; 1822 public final int newIndex; 1823 public CountryCallingCodeAndNewIndex(int countryCode, int newIndex) { 1824 this.countryCallingCode = countryCode; 1825 this.newIndex = newIndex; 1826 } 1827 } 1828 1829 /* 1830 * Note that this function does not strictly care the country calling code with 1831 * 3 length (like Morocco: +212), assuming it is enough to use the first two 1832 * digit to compare two phone numbers. 1833 */ 1834 private static CountryCallingCodeAndNewIndex tryGetCountryCallingCodeAndNewIndex( 1835 String str, boolean acceptThailandCase) { 1836 // Rough regexp: 1837 // ^[^0-9*#+]*((\+|0(0|11)\d\d?|166) [^0-9*#+] $ 1838 // 0 1 2 3 45 6 7 89 1839 // 1840 // In all the states, this function ignores separator characters. 1841 // "166" is the special case for the call from Thailand to the US. Uguu! 1842 int state = 0; 1843 int ccc = 0; 1844 final int length = str.length(); 1845 for (int i = 0 ; i < length ; i++ ) { 1846 char ch = str.charAt(i); 1847 switch (state) { 1848 case 0: 1849 if (ch == '+') state = 1; 1850 else if (ch == '0') state = 2; 1851 else if (ch == '1') { 1852 if (acceptThailandCase) { 1853 state = 8; 1854 } else { 1855 return null; 1856 } 1857 } else if (isDialable(ch)) { 1858 return null; 1859 } 1860 break; 1861 1862 case 2: 1863 if (ch == '0') state = 3; 1864 else if (ch == '1') state = 4; 1865 else if (isDialable(ch)) { 1866 return null; 1867 } 1868 break; 1869 1870 case 4: 1871 if (ch == '1') state = 5; 1872 else if (isDialable(ch)) { 1873 return null; 1874 } 1875 break; 1876 1877 case 1: 1878 case 3: 1879 case 5: 1880 case 6: 1881 case 7: 1882 { 1883 int ret = tryGetISODigit(ch); 1884 if (ret > 0) { 1885 ccc = ccc * 10 + ret; 1886 if (ccc >= 100 || isCountryCallingCode(ccc)) { 1887 return new CountryCallingCodeAndNewIndex(ccc, i + 1); 1888 } 1889 if (state == 1 || state == 3 || state == 5) { 1890 state = 6; 1891 } else { 1892 state++; 1893 } 1894 } else if (isDialable(ch)) { 1895 return null; 1896 } 1897 } 1898 break; 1899 case 8: 1900 if (ch == '6') state = 9; 1901 else if (isDialable(ch)) { 1902 return null; 1903 } 1904 break; 1905 case 9: 1906 if (ch == '6') { 1907 return new CountryCallingCodeAndNewIndex(66, i + 1); 1908 } else { 1909 return null; 1910 } 1911 default: 1912 return null; 1913 } 1914 } 1915 1916 return null; 1917 } 1918 1919 /** 1920 * Currently this function simply ignore the first digit assuming it is 1921 * trunk prefix. Actually trunk prefix is different in each country. 1922 * 1923 * e.g. 1924 * "+79161234567" equals "89161234567" (Russian trunk digit is 8) 1925 * "+33123456789" equals "0123456789" (French trunk digit is 0) 1926 * 1927 */ 1928 private static int tryGetTrunkPrefixOmittedIndex(String str, int currentIndex) { 1929 int length = str.length(); 1930 for (int i = currentIndex ; i < length ; i++) { 1931 final char ch = str.charAt(i); 1932 if (tryGetISODigit(ch) >= 0) { 1933 return i + 1; 1934 } else if (isDialable(ch)) { 1935 return -1; 1936 } 1937 } 1938 return -1; 1939 } 1940 1941 /** 1942 * Return true if the prefix of "str" is "ignorable". Here, "ignorable" means 1943 * that "str" has only one digit and separater characters. The one digit is 1944 * assumed to be trunk prefix. 1945 */ 1946 private static boolean checkPrefixIsIgnorable(final String str, 1947 int forwardIndex, int backwardIndex) { 1948 boolean trunk_prefix_was_read = false; 1949 while (backwardIndex >= forwardIndex) { 1950 if (tryGetISODigit(str.charAt(backwardIndex)) >= 0) { 1951 if (trunk_prefix_was_read) { 1952 // More than one digit appeared, meaning that "a" and "b" 1953 // is different. 1954 return false; 1955 } else { 1956 // Ignore just one digit, assuming it is trunk prefix. 1957 trunk_prefix_was_read = true; 1958 } 1959 } else if (isDialable(str.charAt(backwardIndex))) { 1960 // Trunk prefix is a digit, not "*", "#"... 1961 return false; 1962 } 1963 backwardIndex--; 1964 } 1965 1966 return true; 1967 } 1968 1969 //==== End of utility methods used only in compareStrictly() ===== 1970} 1971