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