PhoneNumberUtils.java revision db1f4993989bb03cae2f3e008d4db3e49577aa73
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 if ((bytes[offset] & 0xff) == TOA_International) { 693 prependPlus = true; 694 } 695 696 internalCalledPartyBCDFragmentToString( 697 ret, bytes, offset + 1, length - 1); 698 699 if (prependPlus && ret.length() == 0) { 700 // If the only thing there is a prepended plus, return "" 701 return ""; 702 } 703 704 if (prependPlus) { 705 // This is an "international number" and should have 706 // a plus prepended to the dialing number. But there 707 // can also be Gsm MMI codes as defined in TS 22.030 6.5.2 708 // so we need to handle those also. 709 // 710 // http://web.telia.com/~u47904776/gsmkode.htm is a 711 // has a nice list of some of these GSM codes. 712 // 713 // Examples are: 714 // **21*+886988171479# 715 // **21*8311234567# 716 // *21# 717 // #21# 718 // *#21# 719 // *31#+11234567890 720 // #31#+18311234567 721 // #31#8311234567 722 // 18311234567 723 // +18311234567# 724 // +18311234567 725 // Odd ball cases that some phones handled 726 // where there is no dialing number so they 727 // append the "+" 728 // *21#+ 729 // **21#+ 730 String retString = ret.toString(); 731 Pattern p = Pattern.compile("(^[#*])(.*)([#*])(.*)(#)$"); 732 Matcher m = p.matcher(retString); 733 if (m.matches()) { 734 if ("".equals(m.group(2))) { 735 // Started with two [#*] ends with # 736 // So no dialing number and we'll just 737 // append a +, this handles **21#+ 738 ret = new StringBuilder(); 739 ret.append(m.group(1)); 740 ret.append(m.group(3)); 741 ret.append(m.group(4)); 742 ret.append(m.group(5)); 743 ret.append("+"); 744 } else { 745 // Starts with [#*] and ends with # 746 // Assume group 4 is a dialing number 747 // such as *21*+1234554# 748 ret = new StringBuilder(); 749 ret.append(m.group(1)); 750 ret.append(m.group(2)); 751 ret.append(m.group(3)); 752 ret.append("+"); 753 ret.append(m.group(4)); 754 ret.append(m.group(5)); 755 } 756 } else { 757 p = Pattern.compile("(^[#*])(.*)([#*])(.*)"); 758 m = p.matcher(retString); 759 if (m.matches()) { 760 // Starts with [#*] and only one other [#*] 761 // Assume the data after last [#*] is dialing 762 // number (i.e. group 4) such as *31#+11234567890. 763 // This also includes the odd ball *21#+ 764 ret = new StringBuilder(); 765 ret.append(m.group(1)); 766 ret.append(m.group(2)); 767 ret.append(m.group(3)); 768 ret.append("+"); 769 ret.append(m.group(4)); 770 } else { 771 // Does NOT start with [#*] just prepend '+' 772 ret = new StringBuilder(); 773 ret.append('+'); 774 ret.append(retString); 775 } 776 } 777 } 778 779 return ret.toString(); 780 } 781 782 private static void 783 internalCalledPartyBCDFragmentToString( 784 StringBuilder sb, byte [] bytes, int offset, int length) { 785 for (int i = offset ; i < length + offset ; i++) { 786 byte b; 787 char c; 788 789 c = bcdToChar((byte)(bytes[i] & 0xf)); 790 791 if (c == 0) { 792 return; 793 } 794 sb.append(c); 795 796 // FIXME(mkf) TS 23.040 9.1.2.3 says 797 // "if a mobile receives 1111 in a position prior to 798 // the last semi-octet then processing shall commense with 799 // the next semi-octet and the intervening 800 // semi-octet shall be ignored" 801 // How does this jive with 24,008 10.5.4.7 802 803 b = (byte)((bytes[i] >> 4) & 0xf); 804 805 if (b == 0xf && i + 1 == length + offset) { 806 //ignore final 0xf 807 break; 808 } 809 810 c = bcdToChar(b); 811 if (c == 0) { 812 return; 813 } 814 815 sb.append(c); 816 } 817 818 } 819 820 /** 821 * Like calledPartyBCDToString, but field does not start with a 822 * TOA byte. For example: SIM ADN extension fields 823 */ 824 825 public static String 826 calledPartyBCDFragmentToString(byte [] bytes, int offset, int length) { 827 StringBuilder ret = new StringBuilder(length * 2); 828 829 internalCalledPartyBCDFragmentToString(ret, bytes, offset, length); 830 831 return ret.toString(); 832 } 833 834 /** returns 0 on invalid value */ 835 private static char 836 bcdToChar(byte b) { 837 if (b < 0xa) { 838 return (char)('0' + b); 839 } else switch (b) { 840 case 0xa: return '*'; 841 case 0xb: return '#'; 842 case 0xc: return PAUSE; 843 case 0xd: return WILD; 844 845 default: return 0; 846 } 847 } 848 849 private static int 850 charToBCD(char c) { 851 if (c >= '0' && c <= '9') { 852 return c - '0'; 853 } else if (c == '*') { 854 return 0xa; 855 } else if (c == '#') { 856 return 0xb; 857 } else if (c == PAUSE) { 858 return 0xc; 859 } else if (c == WILD) { 860 return 0xd; 861 } else { 862 throw new RuntimeException ("invalid char for BCD " + c); 863 } 864 } 865 866 /** 867 * Return true iff the network portion of <code>address</code> is, 868 * as far as we can tell on the device, suitable for use as an SMS 869 * destination address. 870 */ 871 public static boolean isWellFormedSmsAddress(String address) { 872 String networkPortion = 873 PhoneNumberUtils.extractNetworkPortion(address); 874 875 return (!(networkPortion.equals("+") 876 || TextUtils.isEmpty(networkPortion))) 877 && isDialable(networkPortion); 878 } 879 880 public static boolean isGlobalPhoneNumber(String phoneNumber) { 881 if (TextUtils.isEmpty(phoneNumber)) { 882 return false; 883 } 884 885 Matcher match = GLOBAL_PHONE_NUMBER_PATTERN.matcher(phoneNumber); 886 return match.matches(); 887 } 888 889 private static boolean isDialable(String address) { 890 for (int i = 0, count = address.length(); i < count; i++) { 891 if (!isDialable(address.charAt(i))) { 892 return false; 893 } 894 } 895 return true; 896 } 897 898 private static boolean isNonSeparator(String address) { 899 for (int i = 0, count = address.length(); i < count; i++) { 900 if (!isNonSeparator(address.charAt(i))) { 901 return false; 902 } 903 } 904 return true; 905 } 906 /** 907 * Note: calls extractNetworkPortion(), so do not use for 908 * SIM EF[ADN] style records 909 * 910 * Returns null if network portion is empty. 911 */ 912 public static byte[] 913 networkPortionToCalledPartyBCD(String s) { 914 String networkPortion = extractNetworkPortion(s); 915 return numberToCalledPartyBCDHelper(networkPortion, false); 916 } 917 918 /** 919 * Same as {@link #networkPortionToCalledPartyBCD}, but includes a 920 * one-byte length prefix. 921 */ 922 public static byte[] 923 networkPortionToCalledPartyBCDWithLength(String s) { 924 String networkPortion = extractNetworkPortion(s); 925 return numberToCalledPartyBCDHelper(networkPortion, true); 926 } 927 928 /** 929 * Convert a dialing number to BCD byte array 930 * 931 * @param number dialing number string 932 * if the dialing number starts with '+', set to internationl TOA 933 * @return BCD byte array 934 */ 935 public static byte[] 936 numberToCalledPartyBCD(String number) { 937 return numberToCalledPartyBCDHelper(number, false); 938 } 939 940 /** 941 * If includeLength is true, prepend a one-byte length value to 942 * the return array. 943 */ 944 private static byte[] 945 numberToCalledPartyBCDHelper(String number, boolean includeLength) { 946 int numberLenReal = number.length(); 947 int numberLenEffective = numberLenReal; 948 boolean hasPlus = number.indexOf('+') != -1; 949 if (hasPlus) numberLenEffective--; 950 951 if (numberLenEffective == 0) return null; 952 953 int resultLen = (numberLenEffective + 1) / 2; // Encoded numbers require only 4 bits each. 954 int extraBytes = 1; // Prepended TOA byte. 955 if (includeLength) extraBytes++; // Optional prepended length byte. 956 resultLen += extraBytes; 957 958 byte[] result = new byte[resultLen]; 959 960 int digitCount = 0; 961 for (int i = 0; i < numberLenReal; i++) { 962 char c = number.charAt(i); 963 if (c == '+') continue; 964 int shift = ((digitCount & 0x01) == 1) ? 4 : 0; 965 result[extraBytes + (digitCount >> 1)] |= (byte)((charToBCD(c) & 0x0F) << shift); 966 digitCount++; 967 } 968 969 // 1-fill any trailing odd nibble/quartet. 970 if ((digitCount & 0x01) == 1) result[extraBytes + (digitCount >> 1)] |= 0xF0; 971 972 int offset = 0; 973 if (includeLength) result[offset++] = (byte)(resultLen - 1); 974 result[offset] = (byte)(hasPlus ? TOA_International : TOA_Unknown); 975 976 return result; 977 } 978 979 //================ Number formatting ========================= 980 981 /** The current locale is unknown, look for a country code or don't format */ 982 public static final int FORMAT_UNKNOWN = 0; 983 /** NANP formatting */ 984 public static final int FORMAT_NANP = 1; 985 /** Japanese formatting */ 986 public static final int FORMAT_JAPAN = 2; 987 988 /** List of country codes for countries that use the NANP */ 989 private static final String[] NANP_COUNTRIES = new String[] { 990 "US", // United States 991 "CA", // Canada 992 "AS", // American Samoa 993 "AI", // Anguilla 994 "AG", // Antigua and Barbuda 995 "BS", // Bahamas 996 "BB", // Barbados 997 "BM", // Bermuda 998 "VG", // British Virgin Islands 999 "KY", // Cayman Islands 1000 "DM", // Dominica 1001 "DO", // Dominican Republic 1002 "GD", // Grenada 1003 "GU", // Guam 1004 "JM", // Jamaica 1005 "PR", // Puerto Rico 1006 "MS", // Montserrat 1007 "MP", // Northern Mariana Islands 1008 "KN", // Saint Kitts and Nevis 1009 "LC", // Saint Lucia 1010 "VC", // Saint Vincent and the Grenadines 1011 "TT", // Trinidad and Tobago 1012 "TC", // Turks and Caicos Islands 1013 "VI", // U.S. Virgin Islands 1014 }; 1015 1016 /** 1017 * Breaks the given number down and formats it according to the rules 1018 * for the country the number is from. 1019 * 1020 * @param source the phone number to format 1021 * @return a locally acceptable formatting of the input, or the raw input if 1022 * formatting rules aren't known for the number 1023 */ 1024 public static String formatNumber(String source) { 1025 SpannableStringBuilder text = new SpannableStringBuilder(source); 1026 formatNumber(text, getFormatTypeForLocale(Locale.getDefault())); 1027 return text.toString(); 1028 } 1029 1030 /** 1031 * Returns the phone number formatting type for the given locale. 1032 * 1033 * @param locale The locale of interest, usually {@link Locale#getDefault()} 1034 * @return the formatting type for the given locale, or FORMAT_UNKNOWN if the formatting 1035 * rules are not known for the given locale 1036 */ 1037 public static int getFormatTypeForLocale(Locale locale) { 1038 String country = locale.getCountry(); 1039 1040 return getFormatTypeFromCountryCode(country); 1041 } 1042 1043 /** 1044 * Formats a phone number in-place. Currently only supports NANP formatting. 1045 * 1046 * @param text The number to be formatted, will be modified with the formatting 1047 * @param defaultFormattingType The default formatting rules to apply if the number does 1048 * not begin with +<country_code> 1049 */ 1050 public static void formatNumber(Editable text, int defaultFormattingType) { 1051 int formatType = defaultFormattingType; 1052 1053 if (text.length() > 2 && text.charAt(0) == '+') { 1054 if (text.charAt(1) == '1') { 1055 formatType = FORMAT_NANP; 1056 } else if (text.length() >= 3 && text.charAt(1) == '8' 1057 && text.charAt(2) == '1') { 1058 formatType = FORMAT_JAPAN; 1059 } else { 1060 return; 1061 } 1062 } 1063 1064 switch (formatType) { 1065 case FORMAT_NANP: 1066 formatNanpNumber(text); 1067 return; 1068 case FORMAT_JAPAN: 1069 formatJapaneseNumber(text); 1070 return; 1071 } 1072 } 1073 1074 private static final int NANP_STATE_DIGIT = 1; 1075 private static final int NANP_STATE_PLUS = 2; 1076 private static final int NANP_STATE_ONE = 3; 1077 private static final int NANP_STATE_DASH = 4; 1078 1079 /** 1080 * Formats a phone number in-place using the NANP formatting rules. Numbers will be formatted 1081 * as: 1082 * 1083 * <p><code> 1084 * xxxxx 1085 * xxx-xxxx 1086 * xxx-xxx-xxxx 1087 * 1-xxx-xxx-xxxx 1088 * +1-xxx-xxx-xxxx 1089 * </code></p> 1090 * 1091 * @param text the number to be formatted, will be modified with the formatting 1092 */ 1093 public static void formatNanpNumber(Editable text) { 1094 int length = text.length(); 1095 if (length > "+1-nnn-nnn-nnnn".length()) { 1096 // The string is too long to be formatted 1097 return; 1098 } else if (length <= 5) { 1099 // The string is either a shortcode or too short to be formatted 1100 return; 1101 } 1102 1103 CharSequence saved = text.subSequence(0, length); 1104 1105 // Strip the dashes first, as we're going to add them back 1106 int p = 0; 1107 while (p < text.length()) { 1108 if (text.charAt(p) == '-') { 1109 text.delete(p, p + 1); 1110 } else { 1111 p++; 1112 } 1113 } 1114 length = text.length(); 1115 1116 // When scanning the number we record where dashes need to be added, 1117 // if they're non-0 at the end of the scan the dashes will be added in 1118 // the proper places. 1119 int dashPositions[] = new int[3]; 1120 int numDashes = 0; 1121 1122 int state = NANP_STATE_DIGIT; 1123 int numDigits = 0; 1124 for (int i = 0; i < length; i++) { 1125 char c = text.charAt(i); 1126 switch (c) { 1127 case '1': 1128 if (numDigits == 0 || state == NANP_STATE_PLUS) { 1129 state = NANP_STATE_ONE; 1130 break; 1131 } 1132 // fall through 1133 case '2': 1134 case '3': 1135 case '4': 1136 case '5': 1137 case '6': 1138 case '7': 1139 case '8': 1140 case '9': 1141 case '0': 1142 if (state == NANP_STATE_PLUS) { 1143 // Only NANP number supported for now 1144 text.replace(0, length, saved); 1145 return; 1146 } else if (state == NANP_STATE_ONE) { 1147 // Found either +1 or 1, follow it up with a dash 1148 dashPositions[numDashes++] = i; 1149 } else if (state != NANP_STATE_DASH && (numDigits == 3 || numDigits == 6)) { 1150 // Found a digit that should be after a dash that isn't 1151 dashPositions[numDashes++] = i; 1152 } 1153 state = NANP_STATE_DIGIT; 1154 numDigits++; 1155 break; 1156 1157 case '-': 1158 state = NANP_STATE_DASH; 1159 break; 1160 1161 case '+': 1162 if (i == 0) { 1163 // Plus is only allowed as the first character 1164 state = NANP_STATE_PLUS; 1165 break; 1166 } 1167 // Fall through 1168 default: 1169 // Unknown character, bail on formatting 1170 text.replace(0, length, saved); 1171 return; 1172 } 1173 } 1174 1175 if (numDigits == 7) { 1176 // With 7 digits we want xxx-xxxx, not xxx-xxx-x 1177 numDashes--; 1178 } 1179 1180 // Actually put the dashes in place 1181 for (int i = 0; i < numDashes; i++) { 1182 int pos = dashPositions[i]; 1183 text.replace(pos + i, pos + i, "-"); 1184 } 1185 1186 // Remove trailing dashes 1187 int len = text.length(); 1188 while (len > 0) { 1189 if (text.charAt(len - 1) == '-') { 1190 text.delete(len - 1, len); 1191 len--; 1192 } else { 1193 break; 1194 } 1195 } 1196 } 1197 1198 /** 1199 * Formats a phone number in-place using the Japanese formatting rules. 1200 * Numbers will be formatted as: 1201 * 1202 * <p><code> 1203 * 03-xxxx-xxxx 1204 * 090-xxxx-xxxx 1205 * 0120-xxx-xxx 1206 * +81-3-xxxx-xxxx 1207 * +81-90-xxxx-xxxx 1208 * </code></p> 1209 * 1210 * @param text the number to be formatted, will be modified with 1211 * the formatting 1212 */ 1213 public static void formatJapaneseNumber(Editable text) { 1214 JapanesePhoneNumberFormatter.format(text); 1215 } 1216 1217 // Three and four digit phone numbers for either special services, 1218 // or 3-6 digit addresses from the network (eg carrier-originated SMS messages) should 1219 // not match. 1220 // 1221 // This constant used to be 5, but SMS short codes has increased in length and 1222 // can be easily 6 digits now days. Most countries have SMS short code length between 1223 // 3 to 6 digits. The exceptions are 1224 // 1225 // Australia: Short codes are six or eight digits in length, starting with the prefix "19" 1226 // followed by an additional four or six digits and two. 1227 // Czech Republic: Codes are seven digits in length for MO and five (not billed) or 1228 // eight (billed) for MT direction 1229 // 1230 // see http://en.wikipedia.org/wiki/Short_code#Regional_differences for reference 1231 // 1232 // However, in order to loose match 650-555-1212 and 555-1212, we need to set the min match 1233 // to 7. 1234 static final int MIN_MATCH = 7; 1235 1236 /** 1237 * isEmergencyNumber: checks a given number against the list of 1238 * emergency numbers provided by the RIL and SIM card. 1239 * 1240 * @param number the number to look up. 1241 * @return if the number is in the list of emergency numbers 1242 * listed in the ril / sim, then return true, otherwise false. 1243 */ 1244 public static boolean isEmergencyNumber(String number) { 1245 // If the number passed in is null, just return false: 1246 if (number == null) return false; 1247 1248 // Strip the separators from the number before comparing it 1249 // to the list. 1250 number = extractNetworkPortion(number); 1251 1252 // retrieve the list of emergency numbers 1253 String numbers = SystemProperties.get("ro.ril.ecclist"); 1254 1255 if (!TextUtils.isEmpty(numbers)) { 1256 // searches through the comma-separated list for a match, 1257 // return true if one is found. 1258 for (String emergencyNum : numbers.split(",")) { 1259 if (emergencyNum.equals(number)) { 1260 return true; 1261 } 1262 } 1263 // no matches found against the list! 1264 return false; 1265 } 1266 1267 //no ecclist system property, so use our own list. 1268 return (number.equals("112") || number.equals("911")); 1269 } 1270 1271 /** 1272 * isVoiceMailNumber: checks a given number against the voicemail 1273 * number provided by the RIL and SIM card. The caller must have 1274 * the READ_PHONE_STATE credential. 1275 * 1276 * @param number the number to look up. 1277 * @return true if the number is in the list of voicemail. False 1278 * otherwise, including if the caller does not have the permission 1279 * to read the VM number. 1280 * @hide TODO: pending API Council approval 1281 */ 1282 public static boolean isVoiceMailNumber(String number) { 1283 String vmNumber; 1284 1285 try { 1286 vmNumber = TelephonyManager.getDefault().getVoiceMailNumber(); 1287 } catch (SecurityException ex) { 1288 return false; 1289 } 1290 1291 // Strip the separators from the number before comparing it 1292 // to the list. 1293 number = extractNetworkPortion(number); 1294 1295 // compare tolerates null so we need to make sure that we 1296 // don't return true when both are null. 1297 return !TextUtils.isEmpty(number) && compare(number, vmNumber); 1298 } 1299 1300 /** 1301 * Translates any alphabetic letters (i.e. [A-Za-z]) in the 1302 * specified phone number into the equivalent numeric digits, 1303 * according to the phone keypad letter mapping described in 1304 * ITU E.161 and ISO/IEC 9995-8. 1305 * 1306 * @return the input string, with alpha letters converted to numeric 1307 * digits using the phone keypad letter mapping. For example, 1308 * an input of "1-800-GOOG-411" will return "1-800-4664-411". 1309 */ 1310 public static String convertKeypadLettersToDigits(String input) { 1311 if (input == null) { 1312 return input; 1313 } 1314 int len = input.length(); 1315 if (len == 0) { 1316 return input; 1317 } 1318 1319 char[] out = input.toCharArray(); 1320 1321 for (int i = 0; i < len; i++) { 1322 char c = out[i]; 1323 // If this char isn't in KEYPAD_MAP at all, just leave it alone. 1324 out[i] = (char) KEYPAD_MAP.get(c, c); 1325 } 1326 1327 return new String(out); 1328 } 1329 1330 /** 1331 * The phone keypad letter mapping (see ITU E.161 or ISO/IEC 9995-8.) 1332 * TODO: This should come from a resource. 1333 */ 1334 private static final SparseIntArray KEYPAD_MAP = new SparseIntArray(); 1335 static { 1336 KEYPAD_MAP.put('a', '2'); KEYPAD_MAP.put('b', '2'); KEYPAD_MAP.put('c', '2'); 1337 KEYPAD_MAP.put('A', '2'); KEYPAD_MAP.put('B', '2'); KEYPAD_MAP.put('C', '2'); 1338 1339 KEYPAD_MAP.put('d', '3'); KEYPAD_MAP.put('e', '3'); KEYPAD_MAP.put('f', '3'); 1340 KEYPAD_MAP.put('D', '3'); KEYPAD_MAP.put('E', '3'); KEYPAD_MAP.put('F', '3'); 1341 1342 KEYPAD_MAP.put('g', '4'); KEYPAD_MAP.put('h', '4'); KEYPAD_MAP.put('i', '4'); 1343 KEYPAD_MAP.put('G', '4'); KEYPAD_MAP.put('H', '4'); KEYPAD_MAP.put('I', '4'); 1344 1345 KEYPAD_MAP.put('j', '5'); KEYPAD_MAP.put('k', '5'); KEYPAD_MAP.put('l', '5'); 1346 KEYPAD_MAP.put('J', '5'); KEYPAD_MAP.put('K', '5'); KEYPAD_MAP.put('L', '5'); 1347 1348 KEYPAD_MAP.put('m', '6'); KEYPAD_MAP.put('n', '6'); KEYPAD_MAP.put('o', '6'); 1349 KEYPAD_MAP.put('M', '6'); KEYPAD_MAP.put('N', '6'); KEYPAD_MAP.put('O', '6'); 1350 1351 KEYPAD_MAP.put('p', '7'); KEYPAD_MAP.put('q', '7'); KEYPAD_MAP.put('r', '7'); KEYPAD_MAP.put('s', '7'); 1352 KEYPAD_MAP.put('P', '7'); KEYPAD_MAP.put('Q', '7'); KEYPAD_MAP.put('R', '7'); KEYPAD_MAP.put('S', '7'); 1353 1354 KEYPAD_MAP.put('t', '8'); KEYPAD_MAP.put('u', '8'); KEYPAD_MAP.put('v', '8'); 1355 KEYPAD_MAP.put('T', '8'); KEYPAD_MAP.put('U', '8'); KEYPAD_MAP.put('V', '8'); 1356 1357 KEYPAD_MAP.put('w', '9'); KEYPAD_MAP.put('x', '9'); KEYPAD_MAP.put('y', '9'); KEYPAD_MAP.put('z', '9'); 1358 KEYPAD_MAP.put('W', '9'); KEYPAD_MAP.put('X', '9'); KEYPAD_MAP.put('Y', '9'); KEYPAD_MAP.put('Z', '9'); 1359 } 1360 1361 //================ Plus Code formatting ========================= 1362 private static final char PLUS_SIGN_CHAR = '+'; 1363 private static final String PLUS_SIGN_STRING = "+"; 1364 private static final String NANP_IDP_STRING = "011"; 1365 private static final int NANP_LENGTH = 10; 1366 1367 /** 1368 * This function checks if there is a plus sign (+) in the passed-in dialing number. 1369 * If there is, it processes the plus sign based on the default telephone 1370 * numbering plan of the system when the phone is activated and the current 1371 * telephone numbering plan of the system that the phone is camped on. 1372 * Currently, we only support the case that the default and current telephone 1373 * numbering plans are North American Numbering Plan(NANP). 1374 * 1375 * The passed-in dialStr should only contain the valid format as described below, 1376 * 1) the 1st character in the dialStr should be one of the really dialable 1377 * characters listed below 1378 * ISO-LATIN characters 0-9, *, # , + 1379 * 2) the dialStr should already strip out the separator characters, 1380 * every character in the dialStr should be one of the non separator characters 1381 * listed below 1382 * ISO-LATIN characters 0-9, *, # , +, WILD, WAIT, PAUSE 1383 * 1384 * Otherwise, this function returns the dial string passed in 1385 * 1386 * @param dialStr the original dial string 1387 * @return the converted dial string if the current/default countries belong to NANP, 1388 * and if there is the "+" in the original dial string. Otherwise, the original dial 1389 * string returns. 1390 * 1391 * This API is for CDMA only 1392 * 1393 * @hide TODO: pending API Council approval 1394 */ 1395 public static String cdmaCheckAndProcessPlusCode(String dialStr) { 1396 if (!TextUtils.isEmpty(dialStr)) { 1397 if (isReallyDialable(dialStr.charAt(0)) && 1398 isNonSeparator(dialStr)) { 1399 String currIso = SystemProperties.get(PROPERTY_OPERATOR_ISO_COUNTRY, ""); 1400 String defaultIso = SystemProperties.get(PROPERTY_ICC_OPERATOR_ISO_COUNTRY, ""); 1401 if (!TextUtils.isEmpty(currIso) && !TextUtils.isEmpty(defaultIso)) { 1402 return cdmaCheckAndProcessPlusCodeByNumberFormat(dialStr, 1403 getFormatTypeFromCountryCode(currIso), 1404 getFormatTypeFromCountryCode(defaultIso)); 1405 } 1406 } 1407 } 1408 return dialStr; 1409 } 1410 1411 /** 1412 * This function should be called from checkAndProcessPlusCode only 1413 * And it is used for test purpose also. 1414 * 1415 * It checks the dial string by looping through the network portion, 1416 * post dial portion 1, post dial porting 2, etc. If there is any 1417 * plus sign, then process the plus sign. 1418 * Currently, this function supports the plus sign conversion within NANP only. 1419 * Specifically, it handles the plus sign in the following ways: 1420 * 1)+1NANP,remove +, e.g. 1421 * +18475797000 is converted to 18475797000, 1422 * 2)+NANP or +non-NANP Numbers,replace + with the current NANP IDP, e.g, 1423 * +8475797000 is converted to 0118475797000, 1424 * +11875767800 is converted to 01111875767800 1425 * 3)+1NANP in post dial string(s), e.g. 1426 * 8475797000;+18475231753 is converted to 8475797000;18475231753 1427 * 1428 * 1429 * @param dialStr the original dial string 1430 * @param currFormat the numbering system of the current country that the phone is camped on 1431 * @param defaultFormat the numbering system of the country that the phone is activated on 1432 * @return the converted dial string if the current/default countries belong to NANP, 1433 * and if there is the "+" in the original dial string. Otherwise, the original dial 1434 * string returns. 1435 * 1436 * @hide 1437 */ 1438 public static String 1439 cdmaCheckAndProcessPlusCodeByNumberFormat(String dialStr,int currFormat,int defaultFormt) { 1440 String retStr = dialStr; 1441 1442 // Checks if the plus sign character is in the passed-in dial string 1443 if (dialStr != null && 1444 dialStr.lastIndexOf(PLUS_SIGN_STRING) != -1) { 1445 // Format the string based on the rules for the country the number is from, 1446 // and the current country the phone is camped on. 1447 if ((currFormat == defaultFormt) && (currFormat == FORMAT_NANP)) { 1448 // Handle case where default and current telephone numbering plans are NANP. 1449 String postDialStr = null; 1450 String tempDialStr = dialStr; 1451 1452 // Sets the retStr to null since the conversion will be performed below. 1453 retStr = null; 1454 if (DBG) log("checkAndProcessPlusCode,dialStr=" + dialStr); 1455 // This routine is to process the plus sign in the dial string by loop through 1456 // the network portion, post dial portion 1, post dial portion 2... etc. if 1457 // applied 1458 do { 1459 String networkDialStr; 1460 networkDialStr = extractNetworkPortion(tempDialStr); 1461 // Handles the conversion within NANP 1462 networkDialStr = processPlusCodeWithinNanp(networkDialStr); 1463 1464 // Concatenates the string that is converted from network portion 1465 if (!TextUtils.isEmpty(networkDialStr)) { 1466 if (retStr == null) { 1467 retStr = networkDialStr; 1468 } else { 1469 retStr = retStr.concat(networkDialStr); 1470 } 1471 } else { 1472 // This should never happen since we checked the if dialStr is null 1473 // and if it contains the plus sign in the beginning of this function. 1474 // The plus sign is part of the network portion. 1475 Log.e("checkAndProcessPlusCode: null newDialStr", networkDialStr); 1476 return dialStr; 1477 } 1478 postDialStr = extractPostDialPortion(tempDialStr); 1479 if (!TextUtils.isEmpty(postDialStr)) { 1480 int dialableIndex = findDialableIndexFromPostDialStr(postDialStr); 1481 1482 // dialableIndex should always be greater than 0 1483 if (dialableIndex >= 1) { 1484 retStr = appendPwCharBackToOrigDialStr(dialableIndex, 1485 retStr,postDialStr); 1486 // Skips the P/W character, extracts the dialable portion 1487 tempDialStr = postDialStr.substring(dialableIndex); 1488 } else { 1489 // Non-dialable character such as P/W should not be at the end of 1490 // the dial string after P/W processing in CdmaConnection.java 1491 // Set the postDialStr to "" to break out of the loop 1492 if (dialableIndex < 0) { 1493 postDialStr = ""; 1494 } 1495 Log.e("wrong postDialStr=", postDialStr); 1496 } 1497 } 1498 if (DBG) log("checkAndProcessPlusCode,postDialStr=" + postDialStr); 1499 } while (!TextUtils.isEmpty(postDialStr) && !TextUtils.isEmpty(tempDialStr)); 1500 } else { 1501 // TODO: Support NANP international conversion and other telephone numbering plans. 1502 // Currently the phone is never used in non-NANP system, so return the original 1503 // dial string. 1504 Log.e("checkAndProcessPlusCode:non-NANP not supported", dialStr); 1505 } 1506 } 1507 return retStr; 1508 } 1509 1510 // This function gets the default international dialing prefix 1511 private static String getDefaultIdp( ) { 1512 String ps = null; 1513 SystemProperties.get(PROPERTY_IDP_STRING, ps); 1514 if (TextUtils.isEmpty(ps)) { 1515 ps = NANP_IDP_STRING; 1516 } 1517 return ps; 1518 } 1519 1520 private static boolean isTwoToNine (char c) { 1521 if (c >= '2' && c <= '9') { 1522 return true; 1523 } else { 1524 return false; 1525 } 1526 } 1527 1528 private static int getFormatTypeFromCountryCode (String country) { 1529 // Check for the NANP countries 1530 int length = NANP_COUNTRIES.length; 1531 for (int i = 0; i < length; i++) { 1532 if (NANP_COUNTRIES[i].compareToIgnoreCase(country) == 0) { 1533 return FORMAT_NANP; 1534 } 1535 } 1536 if ("jp".compareToIgnoreCase(country) == 0) { 1537 return FORMAT_JAPAN; 1538 } 1539 return FORMAT_UNKNOWN; 1540 } 1541 1542 /** 1543 * This function checks if the passed in string conforms to the NANP format 1544 * i.e. NXX-NXX-XXXX, N is any digit 2-9 and X is any digit 0-9 1545 */ 1546 private static boolean isNanp (String dialStr) { 1547 boolean retVal = false; 1548 if (dialStr != null) { 1549 if (dialStr.length() == NANP_LENGTH) { 1550 if (isTwoToNine(dialStr.charAt(0)) && 1551 isTwoToNine(dialStr.charAt(3))) { 1552 retVal = true; 1553 for (int i=1; i<NANP_LENGTH; i++ ) { 1554 char c=dialStr.charAt(i); 1555 if (!PhoneNumberUtils.isISODigit(c)) { 1556 retVal = false; 1557 break; 1558 } 1559 } 1560 } 1561 } 1562 } else { 1563 Log.e("isNanp: null dialStr passed in", dialStr); 1564 } 1565 return retVal; 1566 } 1567 1568 /** 1569 * This function checks if the passed in string conforms to 1-NANP format 1570 */ 1571 private static boolean isOneNanp(String dialStr) { 1572 boolean retVal = false; 1573 if (dialStr != null) { 1574 String newDialStr = dialStr.substring(1); 1575 if ((dialStr.charAt(0) == '1') && isNanp(newDialStr)) { 1576 retVal = true; 1577 } 1578 } else { 1579 Log.e("isOneNanp: null dialStr passed in", dialStr); 1580 } 1581 return retVal; 1582 } 1583 1584 /** 1585 * This function handles the plus code conversion within NANP CDMA network 1586 * If the number format is 1587 * 1)+1NANP,remove +, 1588 * 2)other than +1NANP, any + numbers,replace + with the current IDP 1589 */ 1590 private static String processPlusCodeWithinNanp(String networkDialStr) { 1591 String retStr = networkDialStr; 1592 1593 if (DBG) log("processPlusCodeWithinNanp,networkDialStr=" + networkDialStr); 1594 // If there is a plus sign at the beginning of the dial string, 1595 // Convert the plus sign to the default IDP since it's an international number 1596 if (networkDialStr != null & 1597 networkDialStr.charAt(0) == PLUS_SIGN_CHAR && 1598 networkDialStr.length() > 1) { 1599 String newStr = networkDialStr.substring(1); 1600 if (isOneNanp(newStr)) { 1601 // Remove the leading plus sign 1602 retStr = newStr; 1603 } else { 1604 String idpStr = getDefaultIdp(); 1605 // Replaces the plus sign with the default IDP 1606 retStr = networkDialStr.replaceFirst("[+]", idpStr); 1607 } 1608 } 1609 if (DBG) log("processPlusCodeWithinNanp,retStr=" + retStr); 1610 return retStr; 1611 } 1612 1613 // This function finds the index of the dialable character(s) 1614 // in the post dial string 1615 private static int findDialableIndexFromPostDialStr(String postDialStr) { 1616 for (int index = 0;index < postDialStr.length();index++) { 1617 char c = postDialStr.charAt(index); 1618 if (isReallyDialable(c)) { 1619 return index; 1620 } 1621 } 1622 return -1; 1623 } 1624 1625 // This function appends the non-diablable P/W character to the original 1626 // dial string based on the dialable index passed in 1627 private static String 1628 appendPwCharBackToOrigDialStr(int dialableIndex,String origStr, String dialStr) { 1629 String retStr; 1630 1631 // There is only 1 P/W character before the dialable characters 1632 if (dialableIndex == 1) { 1633 StringBuilder ret = new StringBuilder(origStr); 1634 ret = ret.append(dialStr.charAt(0)); 1635 retStr = ret.toString(); 1636 } else { 1637 // It means more than 1 P/W characters in the post dial string, 1638 // appends to retStr 1639 String nonDigitStr = dialStr.substring(0,dialableIndex); 1640 retStr = origStr.concat(nonDigitStr); 1641 } 1642 return retStr; 1643 } 1644 1645 //===== Begining of utility methods used in compareLoosely() ===== 1646 1647 /** 1648 * Phone numbers are stored in "lookup" form in the database 1649 * as reversed strings to allow for caller ID lookup 1650 * 1651 * This method takes a phone number and makes a valid SQL "LIKE" 1652 * string that will match the lookup form 1653 * 1654 */ 1655 /** all of a up to len must be an international prefix or 1656 * separators/non-dialing digits 1657 */ 1658 private static boolean 1659 matchIntlPrefix(String a, int len) { 1660 /* '([^0-9*#+pwn]\+[^0-9*#+pwn] | [^0-9*#+pwn]0(0|11)[^0-9*#+pwn] )$' */ 1661 /* 0 1 2 3 45 */ 1662 1663 int state = 0; 1664 for (int i = 0 ; i < len ; i++) { 1665 char c = a.charAt(i); 1666 1667 switch (state) { 1668 case 0: 1669 if (c == '+') state = 1; 1670 else if (c == '0') state = 2; 1671 else if (isNonSeparator(c)) return false; 1672 break; 1673 1674 case 2: 1675 if (c == '0') state = 3; 1676 else if (c == '1') state = 4; 1677 else if (isNonSeparator(c)) return false; 1678 break; 1679 1680 case 4: 1681 if (c == '1') state = 5; 1682 else if (isNonSeparator(c)) return false; 1683 break; 1684 1685 default: 1686 if (isNonSeparator(c)) return false; 1687 break; 1688 1689 } 1690 } 1691 1692 return state == 1 || state == 3 || state == 5; 1693 } 1694 1695 /** all of 'a' up to len must be a (+|00|011)country code) 1696 * We're fast and loose with the country code. Any \d{1,3} matches */ 1697 private static boolean 1698 matchIntlPrefixAndCC(String a, int len) { 1699 /* [^0-9*#+pwn]*(\+|0(0|11)\d\d?\d? [^0-9*#+pwn] $ */ 1700 /* 0 1 2 3 45 6 7 8 */ 1701 1702 int state = 0; 1703 for (int i = 0 ; i < len ; i++ ) { 1704 char c = a.charAt(i); 1705 1706 switch (state) { 1707 case 0: 1708 if (c == '+') state = 1; 1709 else if (c == '0') state = 2; 1710 else if (isNonSeparator(c)) return false; 1711 break; 1712 1713 case 2: 1714 if (c == '0') state = 3; 1715 else if (c == '1') state = 4; 1716 else if (isNonSeparator(c)) return false; 1717 break; 1718 1719 case 4: 1720 if (c == '1') state = 5; 1721 else if (isNonSeparator(c)) return false; 1722 break; 1723 1724 case 1: 1725 case 3: 1726 case 5: 1727 if (isISODigit(c)) state = 6; 1728 else if (isNonSeparator(c)) return false; 1729 break; 1730 1731 case 6: 1732 case 7: 1733 if (isISODigit(c)) state++; 1734 else if (isNonSeparator(c)) return false; 1735 break; 1736 1737 default: 1738 if (isNonSeparator(c)) return false; 1739 } 1740 } 1741 1742 return state == 6 || state == 7 || state == 8; 1743 } 1744 1745 /** all of 'a' up to len must match non-US trunk prefix ('0') */ 1746 private static boolean 1747 matchTrunkPrefix(String a, int len) { 1748 boolean found; 1749 1750 found = false; 1751 1752 for (int i = 0 ; i < len ; i++) { 1753 char c = a.charAt(i); 1754 1755 if (c == '0' && !found) { 1756 found = true; 1757 } else if (isNonSeparator(c)) { 1758 return false; 1759 } 1760 } 1761 1762 return found; 1763 } 1764 1765 //===== End of utility methods used only in compareLoosely() ===== 1766 1767 //===== Beggining of utility methods used only in compareStrictly() ==== 1768 1769 /* 1770 * If true, the number is country calling code. 1771 */ 1772 private static final boolean COUNTLY_CALLING_CALL[] = { 1773 true, true, false, false, false, false, false, true, false, false, 1774 false, false, false, false, false, false, false, false, false, false, 1775 true, false, false, false, false, false, false, true, true, false, 1776 true, true, true, true, true, false, true, false, false, true, 1777 true, false, false, true, true, true, true, true, true, true, 1778 false, true, true, true, true, true, true, true, true, false, 1779 true, true, true, true, true, true, true, false, false, false, 1780 false, false, false, false, false, false, false, false, false, false, 1781 false, true, true, true, true, false, true, false, false, true, 1782 true, true, true, true, true, true, false, false, true, false, 1783 }; 1784 private static final int CCC_LENGTH = COUNTLY_CALLING_CALL.length; 1785 1786 /** 1787 * @return true when input is valid Country Calling Code. 1788 */ 1789 private static boolean isCountryCallingCode(int countryCallingCodeCandidate) { 1790 return countryCallingCodeCandidate > 0 && countryCallingCodeCandidate < CCC_LENGTH && 1791 COUNTLY_CALLING_CALL[countryCallingCodeCandidate]; 1792 } 1793 1794 /** 1795 * Returns interger corresponding to the input if input "ch" is 1796 * ISO-LATIN characters 0-9. 1797 * Returns -1 otherwise 1798 */ 1799 private static int tryGetISODigit(char ch) { 1800 if ('0' <= ch && ch <= '9') { 1801 return ch - '0'; 1802 } else { 1803 return -1; 1804 } 1805 } 1806 1807 private static class CountryCallingCodeAndNewIndex { 1808 public final int countryCallingCode; 1809 public final int newIndex; 1810 public CountryCallingCodeAndNewIndex(int countryCode, int newIndex) { 1811 this.countryCallingCode = countryCode; 1812 this.newIndex = newIndex; 1813 } 1814 } 1815 1816 /* 1817 * Note that this function does not strictly care the country calling code with 1818 * 3 length (like Morocco: +212), assuming it is enough to use the first two 1819 * digit to compare two phone numbers. 1820 */ 1821 private static CountryCallingCodeAndNewIndex tryGetCountryCallingCodeAndNewIndex( 1822 String str, boolean acceptThailandCase) { 1823 // Rough regexp: 1824 // ^[^0-9*#+]*((\+|0(0|11)\d\d?|166) [^0-9*#+] $ 1825 // 0 1 2 3 45 6 7 89 1826 // 1827 // In all the states, this function ignores separator characters. 1828 // "166" is the special case for the call from Thailand to the US. Uguu! 1829 int state = 0; 1830 int ccc = 0; 1831 final int length = str.length(); 1832 for (int i = 0 ; i < length ; i++ ) { 1833 char ch = str.charAt(i); 1834 switch (state) { 1835 case 0: 1836 if (ch == '+') state = 1; 1837 else if (ch == '0') state = 2; 1838 else if (ch == '1') { 1839 if (acceptThailandCase) { 1840 state = 8; 1841 } else { 1842 return null; 1843 } 1844 } else if (isDialable(ch)) { 1845 return null; 1846 } 1847 break; 1848 1849 case 2: 1850 if (ch == '0') state = 3; 1851 else if (ch == '1') state = 4; 1852 else if (isDialable(ch)) { 1853 return null; 1854 } 1855 break; 1856 1857 case 4: 1858 if (ch == '1') state = 5; 1859 else if (isDialable(ch)) { 1860 return null; 1861 } 1862 break; 1863 1864 case 1: 1865 case 3: 1866 case 5: 1867 case 6: 1868 case 7: 1869 { 1870 int ret = tryGetISODigit(ch); 1871 if (ret > 0) { 1872 ccc = ccc * 10 + ret; 1873 if (ccc >= 100 || isCountryCallingCode(ccc)) { 1874 return new CountryCallingCodeAndNewIndex(ccc, i + 1); 1875 } 1876 if (state == 1 || state == 3 || state == 5) { 1877 state = 6; 1878 } else { 1879 state++; 1880 } 1881 } else if (isDialable(ch)) { 1882 return null; 1883 } 1884 } 1885 break; 1886 case 8: 1887 if (ch == '6') state = 9; 1888 else if (isDialable(ch)) { 1889 return null; 1890 } 1891 break; 1892 case 9: 1893 if (ch == '6') { 1894 return new CountryCallingCodeAndNewIndex(66, i + 1); 1895 } else { 1896 return null; 1897 } 1898 default: 1899 return null; 1900 } 1901 } 1902 1903 return null; 1904 } 1905 1906 /** 1907 * Currently this function simply ignore the first digit assuming it is 1908 * trunk prefix. Actually trunk prefix is different in each country. 1909 * 1910 * e.g. 1911 * "+79161234567" equals "89161234567" (Russian trunk digit is 8) 1912 * "+33123456789" equals "0123456789" (French trunk digit is 0) 1913 * 1914 */ 1915 private static int tryGetTrunkPrefixOmittedIndex(String str, int currentIndex) { 1916 int length = str.length(); 1917 for (int i = currentIndex ; i < length ; i++) { 1918 final char ch = str.charAt(i); 1919 if (tryGetISODigit(ch) >= 0) { 1920 return i + 1; 1921 } else if (isDialable(ch)) { 1922 return -1; 1923 } 1924 } 1925 return -1; 1926 } 1927 1928 /** 1929 * Return true if the prefix of "str" is "ignorable". Here, "ignorable" means 1930 * that "str" has only one digit and separater characters. The one digit is 1931 * assumed to be trunk prefix. 1932 */ 1933 private static boolean checkPrefixIsIgnorable(final String str, 1934 int forwardIndex, int backwardIndex) { 1935 boolean trunk_prefix_was_read = false; 1936 while (backwardIndex >= forwardIndex) { 1937 if (tryGetISODigit(str.charAt(backwardIndex)) >= 0) { 1938 if (trunk_prefix_was_read) { 1939 // More than one digit appeared, meaning that "a" and "b" 1940 // is different. 1941 return false; 1942 } else { 1943 // Ignore just one digit, assuming it is trunk prefix. 1944 trunk_prefix_was_read = true; 1945 } 1946 } else if (isDialable(str.charAt(backwardIndex))) { 1947 // Trunk prefix is a digit, not "*", "#"... 1948 return false; 1949 } 1950 backwardIndex--; 1951 } 1952 1953 return true; 1954 } 1955 1956 //==== End of utility methods used only in compareStrictly() ===== 1957} 1958