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