PhoneNumberUtils.java revision 18e7b98c1c7e3cf340e39e93be67b777e7036cc4
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_IDP_STRING; 33 34import java.util.Locale; 35import java.util.regex.Matcher; 36import java.util.regex.Pattern; 37 38/** 39 * Various utilities for dealing with phone number strings. 40 */ 41public class PhoneNumberUtils 42{ 43 /* 44 * Special characters 45 * 46 * (See "What is a phone number?" doc) 47 * 'p' --- GSM pause character, same as comma 48 * 'n' --- GSM wild character 49 * 'w' --- GSM wait character 50 */ 51 public static final char PAUSE = ','; 52 public static final char WAIT = ';'; 53 public static final char WILD = 'N'; 54 55 /* 56 * TOA = TON + NPI 57 * See TS 24.008 section 10.5.4.7 for details. 58 * These are the only really useful TOA values 59 */ 60 public static final int TOA_International = 0x91; 61 public static final int TOA_Unknown = 0x81; 62 63 static final String LOG_TAG = "PhoneNumberUtils"; 64 private static final boolean DBG = false; 65 66 /* 67 * global-phone-number = ["+"] 1*( DIGIT / written-sep ) 68 * written-sep = ("-"/".") 69 */ 70 private static final Pattern GLOBAL_PHONE_NUMBER_PATTERN = 71 Pattern.compile("[\\+]?[0-9.-]+"); 72 73 /** True if c is ISO-LATIN characters 0-9 */ 74 public static boolean 75 isISODigit (char c) { 76 return c >= '0' && c <= '9'; 77 } 78 79 /** True if c is ISO-LATIN characters 0-9, *, # */ 80 public final static boolean 81 is12Key(char c) { 82 return (c >= '0' && c <= '9') || c == '*' || c == '#'; 83 } 84 85 /** True if c is ISO-LATIN characters 0-9, *, # , +, WILD */ 86 public final static boolean 87 isDialable(char c) { 88 return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+' || c == WILD; 89 } 90 91 /** True if c is ISO-LATIN characters 0-9, *, # , + (no WILD) */ 92 public final static boolean 93 isReallyDialable(char c) { 94 return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+'; 95 } 96 97 /** True if c is ISO-LATIN characters 0-9, *, # , +, WILD, WAIT, PAUSE */ 98 public final static boolean 99 isNonSeparator(char c) { 100 return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+' 101 || c == WILD || c == WAIT || c == PAUSE; 102 } 103 104 /** This any anything to the right of this char is part of the 105 * post-dial string (eg this is PAUSE or WAIT) 106 */ 107 public final static boolean 108 isStartsPostDial (char c) { 109 return c == PAUSE || c == WAIT; 110 } 111 112 /** Extracts the phone number from an Intent. 113 * 114 * @param intent the intent to get the number of 115 * @param context a context to use for database access 116 * 117 * @return the phone number that would be called by the intent, or 118 * <code>null</code> if the number cannot be found. 119 */ 120 public static String getNumberFromIntent(Intent intent, Context context) { 121 String number = null; 122 123 Uri uri = intent.getData(); 124 String scheme = uri.getScheme(); 125 126 if (scheme.equals("tel")) { 127 return uri.getSchemeSpecificPart(); 128 } 129 130 if (scheme.equals("voicemail")) { 131 return TelephonyManager.getDefault().getVoiceMailNumber(); 132 } 133 134 if (context == null) { 135 return null; 136 } 137 138 String type = intent.resolveType(context); 139 String phoneColumn = null; 140 141 // Correctly read out the phone entry based on requested provider 142 final String authority = uri.getAuthority(); 143 if (Contacts.AUTHORITY.equals(authority)) { 144 phoneColumn = Contacts.People.Phones.NUMBER; 145 } else if (ContactsContract.AUTHORITY.equals(authority)) { 146 phoneColumn = ContactsContract.CommonDataKinds.Phone.NUMBER; 147 } 148 149 final Cursor c = context.getContentResolver().query(uri, new String[] { 150 phoneColumn 151 }, null, null, null); 152 if (c != null) { 153 try { 154 if (c.moveToFirst()) { 155 number = c.getString(c.getColumnIndex(phoneColumn)); 156 } 157 } finally { 158 c.close(); 159 } 160 } 161 162 return number; 163 } 164 165 /** Extracts the network address portion and canonicalizes 166 * (filters out separators.) 167 * Network address portion is everything up to DTMF control digit 168 * separators (pause or wait), but without non-dialable characters. 169 * 170 * Please note that the GSM wild character is allowed in the result. 171 * This must be resolved before dialing. 172 * 173 * Allows + only in the first position in the result string. 174 * 175 * Returns null if phoneNumber == null 176 */ 177 public static String 178 extractNetworkPortion(String phoneNumber) { 179 if (phoneNumber == null) { 180 return null; 181 } 182 183 int len = phoneNumber.length(); 184 StringBuilder ret = new StringBuilder(len); 185 boolean firstCharAdded = false; 186 187 for (int i = 0; i < len; i++) { 188 char c = phoneNumber.charAt(i); 189 if (isDialable(c) && (c != '+' || !firstCharAdded)) { 190 firstCharAdded = true; 191 ret.append(c); 192 } else if (isStartsPostDial (c)) { 193 break; 194 } 195 } 196 197 return ret.toString(); 198 } 199 200 /** 201 * Strips separators from a phone number string. 202 * @param phoneNumber phone number to strip. 203 * @return phone string stripped of separators. 204 */ 205 public static String stripSeparators(String phoneNumber) { 206 if (phoneNumber == null) { 207 return null; 208 } 209 int len = phoneNumber.length(); 210 StringBuilder ret = new StringBuilder(len); 211 212 for (int i = 0; i < len; i++) { 213 char c = phoneNumber.charAt(i); 214 if (isNonSeparator(c)) { 215 ret.append(c); 216 } 217 } 218 219 return ret.toString(); 220 } 221 222 /** or -1 if both are negative */ 223 static private int 224 minPositive (int a, int b) { 225 if (a >= 0 && b >= 0) { 226 return (a < b) ? a : b; 227 } else if (a >= 0) { /* && b < 0 */ 228 return a; 229 } else if (b >= 0) { /* && a < 0 */ 230 return b; 231 } else { /* a < 0 && b < 0 */ 232 return -1; 233 } 234 } 235 236 private static void log(String msg) { 237 Log.d(LOG_TAG, msg); 238 } 239 /** index of the last character of the network portion 240 * (eg anything after is a post-dial string) 241 */ 242 static private int 243 indexOfLastNetworkChar(String a) { 244 int pIndex, wIndex; 245 int origLength; 246 int trimIndex; 247 248 origLength = a.length(); 249 250 pIndex = a.indexOf(PAUSE); 251 wIndex = a.indexOf(WAIT); 252 253 trimIndex = minPositive(pIndex, wIndex); 254 255 if (trimIndex < 0) { 256 return origLength - 1; 257 } else { 258 return trimIndex - 1; 259 } 260 } 261 262 /** 263 * Extracts the post-dial sequence of DTMF control digits, pauses, and 264 * waits. Strips separators. This string may be empty, but will not be null 265 * unless phoneNumber == null. 266 * 267 * Returns null if phoneNumber == null 268 */ 269 270 public static String 271 extractPostDialPortion(String phoneNumber) { 272 if (phoneNumber == null) return null; 273 274 int trimIndex; 275 StringBuilder ret = new StringBuilder(); 276 277 trimIndex = indexOfLastNetworkChar (phoneNumber); 278 279 for (int i = trimIndex + 1, s = phoneNumber.length() 280 ; i < s; i++ 281 ) { 282 char c = phoneNumber.charAt(i); 283 if (isNonSeparator(c)) { 284 ret.append(c); 285 } 286 } 287 288 return ret.toString(); 289 } 290 291 /** 292 * Compare phone numbers a and b, return true if they're identical 293 * enough for caller ID purposes. 294 * 295 * - Compares from right to left 296 * - requires MIN_MATCH (5) characters to match 297 * - handles common trunk prefixes and international prefixes 298 * (basically, everything except the Russian trunk prefix) 299 * 300 * Tolerates nulls 301 */ 302 public static boolean 303 compare(String a, String b) { 304 int ia, ib; 305 int matched; 306 307 if (a == null || b == null) return a == b; 308 309 if (a.length() == 0 || b.length() == 0) { 310 return false; 311 } 312 313 ia = indexOfLastNetworkChar (a); 314 ib = indexOfLastNetworkChar (b); 315 matched = 0; 316 317 while (ia >= 0 && ib >=0) { 318 char ca, cb; 319 boolean skipCmp = false; 320 321 ca = a.charAt(ia); 322 323 if (!isDialable(ca)) { 324 ia--; 325 skipCmp = true; 326 } 327 328 cb = b.charAt(ib); 329 330 if (!isDialable(cb)) { 331 ib--; 332 skipCmp = true; 333 } 334 335 if (!skipCmp) { 336 if (cb != ca && ca != WILD && cb != WILD) { 337 break; 338 } 339 ia--; ib--; matched++; 340 } 341 } 342 343 if (matched < MIN_MATCH) { 344 int aLen = a.length(); 345 346 // if the input strings match, but their lengths < MIN_MATCH, 347 // treat them as equal. 348 if (aLen == b.length() && aLen == matched) { 349 return true; 350 } 351 return false; 352 } 353 354 // At least one string has matched completely; 355 if (matched >= MIN_MATCH && (ia < 0 || ib < 0)) { 356 return true; 357 } 358 359 /* 360 * Now, what remains must be one of the following for a 361 * match: 362 * 363 * - a '+' on one and a '00' or a '011' on the other 364 * - a '0' on one and a (+,00)<country code> on the other 365 * (for this, a '0' and a '00' prefix would have succeeded above) 366 */ 367 368 if (matchIntlPrefix(a, ia + 1) 369 && matchIntlPrefix (b, ib +1) 370 ) { 371 return true; 372 } 373 374 if (matchTrunkPrefix(a, ia + 1) 375 && matchIntlPrefixAndCC(b, ib +1) 376 ) { 377 return true; 378 } 379 380 if (matchTrunkPrefix(b, ib + 1) 381 && matchIntlPrefixAndCC(a, ia +1) 382 ) { 383 return true; 384 } 385 386 return false; 387 } 388 389 /** 390 * Returns the rightmost MIN_MATCH (5) characters in the network portion 391 * in *reversed* order 392 * 393 * This can be used to do a database lookup against the column 394 * that stores getStrippedReversed() 395 * 396 * Returns null if phoneNumber == null 397 */ 398 public static String 399 toCallerIDMinMatch(String phoneNumber) { 400 String np = extractNetworkPortion(phoneNumber); 401 return internalGetStrippedReversed(np, MIN_MATCH); 402 } 403 404 /** 405 * Returns the network portion reversed. 406 * This string is intended to go into an index column for a 407 * database lookup. 408 * 409 * Returns null if phoneNumber == null 410 */ 411 public static String 412 getStrippedReversed(String phoneNumber) { 413 String np = extractNetworkPortion(phoneNumber); 414 415 if (np == null) return null; 416 417 return internalGetStrippedReversed(np, np.length()); 418 } 419 420 /** 421 * Returns the last numDigits of the reversed phone number 422 * Returns null if np == null 423 */ 424 private static String 425 internalGetStrippedReversed(String np, int numDigits) { 426 if (np == null) return null; 427 428 StringBuilder ret = new StringBuilder(numDigits); 429 int length = np.length(); 430 431 for (int i = length - 1, s = length 432 ; i >= 0 && (s - i) <= numDigits ; i-- 433 ) { 434 char c = np.charAt(i); 435 436 ret.append(c); 437 } 438 439 return ret.toString(); 440 } 441 442 /** 443 * Basically: makes sure there's a + in front of a 444 * TOA_International number 445 * 446 * Returns null if s == null 447 */ 448 public static String 449 stringFromStringAndTOA(String s, int TOA) { 450 if (s == null) return null; 451 452 if (TOA == TOA_International && s.length() > 0 && s.charAt(0) != '+') { 453 return "+" + s; 454 } 455 456 return s; 457 } 458 459 /** 460 * Returns the TOA for the given dial string 461 * Basically, returns TOA_International if there's a + prefix 462 */ 463 464 public static int 465 toaFromString(String s) { 466 if (s != null && s.length() > 0 && s.charAt(0) == '+') { 467 return TOA_International; 468 } 469 470 return TOA_Unknown; 471 } 472 473 /** 474 * Phone numbers are stored in "lookup" form in the database 475 * as reversed strings to allow for caller ID lookup 476 * 477 * This method takes a phone number and makes a valid SQL "LIKE" 478 * string that will match the lookup form 479 * 480 */ 481 /** all of a up to len must be an international prefix or 482 * separators/non-dialing digits 483 */ 484 private static boolean 485 matchIntlPrefix(String a, int len) { 486 /* '([^0-9*#+pwn]\+[^0-9*#+pwn] | [^0-9*#+pwn]0(0|11)[^0-9*#+pwn] )$' */ 487 /* 0 1 2 3 45 */ 488 489 int state = 0; 490 for (int i = 0 ; i < len ; i++) { 491 char c = a.charAt(i); 492 493 switch (state) { 494 case 0: 495 if (c == '+') state = 1; 496 else if (c == '0') state = 2; 497 else if (isNonSeparator(c)) return false; 498 break; 499 500 case 2: 501 if (c == '0') state = 3; 502 else if (c == '1') state = 4; 503 else if (isNonSeparator(c)) return false; 504 break; 505 506 case 4: 507 if (c == '1') state = 5; 508 else if (isNonSeparator(c)) return false; 509 break; 510 511 default: 512 if (isNonSeparator(c)) return false; 513 break; 514 515 } 516 } 517 518 return state == 1 || state == 3 || state == 5; 519 } 520 521 /** 522 * 3GPP TS 24.008 10.5.4.7 523 * Called Party BCD Number 524 * 525 * See Also TS 51.011 10.5.1 "dialing number/ssc string" 526 * and TS 11.11 "10.3.1 EF adn (Abbreviated dialing numbers)" 527 * 528 * @param bytes the data buffer 529 * @param offset should point to the TOA (aka. TON/NPI) octet after the length byte 530 * @param length is the number of bytes including TOA byte 531 * and must be at least 2 532 * 533 * @return partial string on invalid decode 534 * 535 * FIXME(mkf) support alphanumeric address type 536 * currently implemented in SMSMessage.getAddress() 537 */ 538 public static String 539 calledPartyBCDToString (byte[] bytes, int offset, int length) { 540 boolean prependPlus = false; 541 StringBuilder ret = new StringBuilder(1 + length * 2); 542 543 if (length < 2) { 544 return ""; 545 } 546 547 if ((bytes[offset] & 0xff) == TOA_International) { 548 prependPlus = true; 549 } 550 551 internalCalledPartyBCDFragmentToString( 552 ret, bytes, offset + 1, length - 1); 553 554 if (prependPlus && ret.length() == 0) { 555 // If the only thing there is a prepended plus, return "" 556 return ""; 557 } 558 559 if (prependPlus) { 560 // This is an "international number" and should have 561 // a plus prepended to the dialing number. But there 562 // can also be Gsm MMI codes as defined in TS 22.030 6.5.2 563 // so we need to handle those also. 564 // 565 // http://web.telia.com/~u47904776/gsmkode.htm is a 566 // has a nice list of some of these GSM codes. 567 // 568 // Examples are: 569 // **21*+886988171479# 570 // **21*8311234567# 571 // *21# 572 // #21# 573 // *#21# 574 // *31#+11234567890 575 // #31#+18311234567 576 // #31#8311234567 577 // 18311234567 578 // +18311234567# 579 // +18311234567 580 // Odd ball cases that some phones handled 581 // where there is no dialing number so they 582 // append the "+" 583 // *21#+ 584 // **21#+ 585 String retString = ret.toString(); 586 Pattern p = Pattern.compile("(^[#*])(.*)([#*])(.*)(#)$"); 587 Matcher m = p.matcher(retString); 588 if (m.matches()) { 589 if ("".equals(m.group(2))) { 590 // Started with two [#*] ends with # 591 // So no dialing number and we'll just 592 // append a +, this handles **21#+ 593 ret = new StringBuilder(); 594 ret.append(m.group(1)); 595 ret.append(m.group(3)); 596 ret.append(m.group(4)); 597 ret.append(m.group(5)); 598 ret.append("+"); 599 } else { 600 // Starts with [#*] and ends with # 601 // Assume group 4 is a dialing number 602 // such as *21*+1234554# 603 ret = new StringBuilder(); 604 ret.append(m.group(1)); 605 ret.append(m.group(2)); 606 ret.append(m.group(3)); 607 ret.append("+"); 608 ret.append(m.group(4)); 609 ret.append(m.group(5)); 610 } 611 } else { 612 p = Pattern.compile("(^[#*])(.*)([#*])(.*)"); 613 m = p.matcher(retString); 614 if (m.matches()) { 615 // Starts with [#*] and only one other [#*] 616 // Assume the data after last [#*] is dialing 617 // number (i.e. group 4) such as *31#+11234567890. 618 // This also includes the odd ball *21#+ 619 ret = new StringBuilder(); 620 ret.append(m.group(1)); 621 ret.append(m.group(2)); 622 ret.append(m.group(3)); 623 ret.append("+"); 624 ret.append(m.group(4)); 625 } else { 626 // Does NOT start with [#*] just prepend '+' 627 ret = new StringBuilder(); 628 ret.append('+'); 629 ret.append(retString); 630 } 631 } 632 } 633 634 return ret.toString(); 635 } 636 637 private static void 638 internalCalledPartyBCDFragmentToString( 639 StringBuilder sb, byte [] bytes, int offset, int length) { 640 for (int i = offset ; i < length + offset ; i++) { 641 byte b; 642 char c; 643 644 c = bcdToChar((byte)(bytes[i] & 0xf)); 645 646 if (c == 0) { 647 return; 648 } 649 sb.append(c); 650 651 // FIXME(mkf) TS 23.040 9.1.2.3 says 652 // "if a mobile receives 1111 in a position prior to 653 // the last semi-octet then processing shall commense with 654 // the next semi-octet and the intervening 655 // semi-octet shall be ignored" 656 // How does this jive with 24,008 10.5.4.7 657 658 b = (byte)((bytes[i] >> 4) & 0xf); 659 660 if (b == 0xf && i + 1 == length + offset) { 661 //ignore final 0xf 662 break; 663 } 664 665 c = bcdToChar(b); 666 if (c == 0) { 667 return; 668 } 669 670 sb.append(c); 671 } 672 673 } 674 675 /** 676 * Like calledPartyBCDToString, but field does not start with a 677 * TOA byte. For example: SIM ADN extension fields 678 */ 679 680 public static String 681 calledPartyBCDFragmentToString(byte [] bytes, int offset, int length) { 682 StringBuilder ret = new StringBuilder(length * 2); 683 684 internalCalledPartyBCDFragmentToString(ret, bytes, offset, length); 685 686 return ret.toString(); 687 } 688 689 /** returns 0 on invalid value */ 690 private static char 691 bcdToChar(byte b) { 692 if (b < 0xa) { 693 return (char)('0' + b); 694 } else switch (b) { 695 case 0xa: return '*'; 696 case 0xb: return '#'; 697 case 0xc: return PAUSE; 698 case 0xd: return WILD; 699 700 default: return 0; 701 } 702 } 703 704 private static int 705 charToBCD(char c) { 706 if (c >= '0' && c <= '9') { 707 return c - '0'; 708 } else if (c == '*') { 709 return 0xa; 710 } else if (c == '#') { 711 return 0xb; 712 } else if (c == PAUSE) { 713 return 0xc; 714 } else if (c == WILD) { 715 return 0xd; 716 } else { 717 throw new RuntimeException ("invalid char for BCD " + c); 718 } 719 } 720 721 /** 722 * Return true iff the network portion of <code>address</code> is, 723 * as far as we can tell on the device, suitable for use as an SMS 724 * destination address. 725 */ 726 public static boolean isWellFormedSmsAddress(String address) { 727 String networkPortion = 728 PhoneNumberUtils.extractNetworkPortion(address); 729 730 return (!(networkPortion.equals("+") 731 || TextUtils.isEmpty(networkPortion))) 732 && isDialable(networkPortion); 733 } 734 735 public static boolean isGlobalPhoneNumber(String phoneNumber) { 736 if (TextUtils.isEmpty(phoneNumber)) { 737 return false; 738 } 739 740 Matcher match = GLOBAL_PHONE_NUMBER_PATTERN.matcher(phoneNumber); 741 return match.matches(); 742 } 743 744 private static boolean isDialable(String address) { 745 for (int i = 0, count = address.length(); i < count; i++) { 746 if (!isDialable(address.charAt(i))) { 747 return false; 748 } 749 } 750 return true; 751 } 752 753 private static boolean isNonSeparator(String address) { 754 for (int i = 0, count = address.length(); i < count; i++) { 755 if (!isNonSeparator(address.charAt(i))) { 756 return false; 757 } 758 } 759 return true; 760 } 761 /** 762 * Note: calls extractNetworkPortion(), so do not use for 763 * SIM EF[ADN] style records 764 * 765 * Returns null if network portion is empty. 766 */ 767 public static byte[] 768 networkPortionToCalledPartyBCD(String s) { 769 String networkPortion = extractNetworkPortion(s); 770 return numberToCalledPartyBCDHelper(networkPortion, false); 771 } 772 773 /** 774 * Same as {@link #networkPortionToCalledPartyBCD}, but includes a 775 * one-byte length prefix. 776 */ 777 public static byte[] 778 networkPortionToCalledPartyBCDWithLength(String s) { 779 String networkPortion = extractNetworkPortion(s); 780 return numberToCalledPartyBCDHelper(networkPortion, true); 781 } 782 783 /** 784 * Convert a dialing number to BCD byte array 785 * 786 * @param number dialing number string 787 * if the dialing number starts with '+', set to internationl TOA 788 * @return BCD byte array 789 */ 790 public static byte[] 791 numberToCalledPartyBCD(String number) { 792 return numberToCalledPartyBCDHelper(number, false); 793 } 794 795 /** 796 * If includeLength is true, prepend a one-byte length value to 797 * the return array. 798 */ 799 private static byte[] 800 numberToCalledPartyBCDHelper(String number, boolean includeLength) { 801 int numberLenReal = number.length(); 802 int numberLenEffective = numberLenReal; 803 boolean hasPlus = number.indexOf('+') != -1; 804 if (hasPlus) numberLenEffective--; 805 806 if (numberLenEffective == 0) return null; 807 808 int resultLen = (numberLenEffective + 1) / 2; // Encoded numbers require only 4 bits each. 809 int extraBytes = 1; // Prepended TOA byte. 810 if (includeLength) extraBytes++; // Optional prepended length byte. 811 resultLen += extraBytes; 812 813 byte[] result = new byte[resultLen]; 814 815 int digitCount = 0; 816 for (int i = 0; i < numberLenReal; i++) { 817 char c = number.charAt(i); 818 if (c == '+') continue; 819 int shift = ((digitCount & 0x01) == 1) ? 4 : 0; 820 result[extraBytes + (digitCount >> 1)] |= (byte)((charToBCD(c) & 0x0F) << shift); 821 digitCount++; 822 } 823 824 // 1-fill any trailing odd nibble/quartet. 825 if ((digitCount & 0x01) == 1) result[extraBytes + (digitCount >> 1)] |= 0xF0; 826 827 int offset = 0; 828 if (includeLength) result[offset++] = (byte)(resultLen - 1); 829 result[offset] = (byte)(hasPlus ? TOA_International : TOA_Unknown); 830 831 return result; 832 } 833 834 /** all of 'a' up to len must match non-US trunk prefix ('0') */ 835 private static boolean 836 matchTrunkPrefix(String a, int len) { 837 boolean found; 838 839 found = false; 840 841 for (int i = 0 ; i < len ; i++) { 842 char c = a.charAt(i); 843 844 if (c == '0' && !found) { 845 found = true; 846 } else if (isNonSeparator(c)) { 847 return false; 848 } 849 } 850 851 return found; 852 } 853 854 /** all of 'a' up to len must be a (+|00|011)country code) 855 * We're fast and loose with the country code. Any \d{1,3} matches */ 856 private static boolean 857 matchIntlPrefixAndCC(String a, int len) { 858 /* [^0-9*#+pwn]*(\+|0(0|11)\d\d?\d? [^0-9*#+pwn] $ */ 859 /* 0 1 2 3 45 6 7 8 */ 860 861 int state = 0; 862 for (int i = 0 ; i < len ; i++ ) { 863 char c = a.charAt(i); 864 865 switch (state) { 866 case 0: 867 if (c == '+') state = 1; 868 else if (c == '0') state = 2; 869 else if (isNonSeparator(c)) return false; 870 break; 871 872 case 2: 873 if (c == '0') state = 3; 874 else if (c == '1') state = 4; 875 else if (isNonSeparator(c)) return false; 876 break; 877 878 case 4: 879 if (c == '1') state = 5; 880 else if (isNonSeparator(c)) return false; 881 break; 882 883 case 1: 884 case 3: 885 case 5: 886 if (isISODigit(c)) state = 6; 887 else if (isNonSeparator(c)) return false; 888 break; 889 890 case 6: 891 case 7: 892 if (isISODigit(c)) state++; 893 else if (isNonSeparator(c)) return false; 894 break; 895 896 default: 897 if (isNonSeparator(c)) return false; 898 } 899 } 900 901 return state == 6 || state == 7 || state == 8; 902 } 903 904 //================ Number formatting ========================= 905 906 /** The current locale is unknown, look for a country code or don't format */ 907 public static final int FORMAT_UNKNOWN = 0; 908 /** NANP formatting */ 909 public static final int FORMAT_NANP = 1; 910 /** Japanese formatting */ 911 public static final int FORMAT_JAPAN = 2; 912 913 /** List of country codes for countries that use the NANP */ 914 private static final String[] NANP_COUNTRIES = new String[] { 915 "US", // United States 916 "CA", // Canada 917 "AS", // American Samoa 918 "AI", // Anguilla 919 "AG", // Antigua and Barbuda 920 "BS", // Bahamas 921 "BB", // Barbados 922 "BM", // Bermuda 923 "VG", // British Virgin Islands 924 "KY", // Cayman Islands 925 "DM", // Dominica 926 "DO", // Dominican Republic 927 "GD", // Grenada 928 "GU", // Guam 929 "JM", // Jamaica 930 "PR", // Puerto Rico 931 "MS", // Montserrat 932 "NP", // Northern Mariana Islands 933 "KN", // Saint Kitts and Nevis 934 "LC", // Saint Lucia 935 "VC", // Saint Vincent and the Grenadines 936 "TT", // Trinidad and Tobago 937 "TC", // Turks and Caicos Islands 938 "VI", // U.S. Virgin Islands 939 }; 940 941 /** 942 * Breaks the given number down and formats it according to the rules 943 * for the country the number is from. 944 * 945 * @param source the phone number to format 946 * @return a locally acceptable formatting of the input, or the raw input if 947 * formatting rules aren't known for the number 948 */ 949 public static String formatNumber(String source) { 950 SpannableStringBuilder text = new SpannableStringBuilder(source); 951 formatNumber(text, getFormatTypeForLocale(Locale.getDefault())); 952 return text.toString(); 953 } 954 955 /** 956 * Returns the phone number formatting type for the given locale. 957 * 958 * @param locale The locale of interest, usually {@link Locale#getDefault()} 959 * @return the formatting type for the given locale, or FORMAT_UNKNOWN if the formatting 960 * rules are not known for the given locale 961 */ 962 public static int getFormatTypeForLocale(Locale locale) { 963 String country = locale.getCountry(); 964 965 // Check for the NANP countries 966 int length = NANP_COUNTRIES.length; 967 for (int i = 0; i < length; i++) { 968 if (NANP_COUNTRIES[i].equals(country)) { 969 return FORMAT_NANP; 970 } 971 } 972 if (locale.equals(Locale.JAPAN)) { 973 return FORMAT_JAPAN; 974 } 975 return FORMAT_UNKNOWN; 976 } 977 978 /** 979 * Formats a phone number in-place. Currently only supports NANP formatting. 980 * 981 * @param text The number to be formatted, will be modified with the formatting 982 * @param defaultFormattingType The default formatting rules to apply if the number does 983 * not begin with +<country_code> 984 */ 985 public static void formatNumber(Editable text, int defaultFormattingType) { 986 int formatType = defaultFormattingType; 987 988 if (text.length() > 2 && text.charAt(0) == '+') { 989 if (text.charAt(1) == '1') { 990 formatType = FORMAT_NANP; 991 } else if (text.length() >= 3 && text.charAt(1) == '8' 992 && text.charAt(2) == '1') { 993 formatType = FORMAT_JAPAN; 994 } else { 995 return; 996 } 997 } 998 999 switch (formatType) { 1000 case FORMAT_NANP: 1001 formatNanpNumber(text); 1002 return; 1003 case FORMAT_JAPAN: 1004 formatJapaneseNumber(text); 1005 return; 1006 } 1007 } 1008 1009 private static final int NANP_STATE_DIGIT = 1; 1010 private static final int NANP_STATE_PLUS = 2; 1011 private static final int NANP_STATE_ONE = 3; 1012 private static final int NANP_STATE_DASH = 4; 1013 1014 /** 1015 * Formats a phone number in-place using the NANP formatting rules. Numbers will be formatted 1016 * as: 1017 * 1018 * <p><code> 1019 * xxxxx 1020 * xxx-xxxx 1021 * xxx-xxx-xxxx 1022 * 1-xxx-xxx-xxxx 1023 * +1-xxx-xxx-xxxx 1024 * </code></p> 1025 * 1026 * @param text the number to be formatted, will be modified with the formatting 1027 */ 1028 public static void formatNanpNumber(Editable text) { 1029 int length = text.length(); 1030 if (length > "+1-nnn-nnn-nnnn".length()) { 1031 // The string is too long to be formatted 1032 return; 1033 } else if (length <= 5) { 1034 // The string is either a shortcode or too short to be formatted 1035 return; 1036 } 1037 1038 CharSequence saved = text.subSequence(0, length); 1039 1040 // Strip the dashes first, as we're going to add them back 1041 int p = 0; 1042 while (p < text.length()) { 1043 if (text.charAt(p) == '-') { 1044 text.delete(p, p + 1); 1045 } else { 1046 p++; 1047 } 1048 } 1049 length = text.length(); 1050 1051 // When scanning the number we record where dashes need to be added, 1052 // if they're non-0 at the end of the scan the dashes will be added in 1053 // the proper places. 1054 int dashPositions[] = new int[3]; 1055 int numDashes = 0; 1056 1057 int state = NANP_STATE_DIGIT; 1058 int numDigits = 0; 1059 for (int i = 0; i < length; i++) { 1060 char c = text.charAt(i); 1061 switch (c) { 1062 case '1': 1063 if (numDigits == 0 || state == NANP_STATE_PLUS) { 1064 state = NANP_STATE_ONE; 1065 break; 1066 } 1067 // fall through 1068 case '2': 1069 case '3': 1070 case '4': 1071 case '5': 1072 case '6': 1073 case '7': 1074 case '8': 1075 case '9': 1076 case '0': 1077 if (state == NANP_STATE_PLUS) { 1078 // Only NANP number supported for now 1079 text.replace(0, length, saved); 1080 return; 1081 } else if (state == NANP_STATE_ONE) { 1082 // Found either +1 or 1, follow it up with a dash 1083 dashPositions[numDashes++] = i; 1084 } else if (state != NANP_STATE_DASH && (numDigits == 3 || numDigits == 6)) { 1085 // Found a digit that should be after a dash that isn't 1086 dashPositions[numDashes++] = i; 1087 } 1088 state = NANP_STATE_DIGIT; 1089 numDigits++; 1090 break; 1091 1092 case '-': 1093 state = NANP_STATE_DASH; 1094 break; 1095 1096 case '+': 1097 if (i == 0) { 1098 // Plus is only allowed as the first character 1099 state = NANP_STATE_PLUS; 1100 break; 1101 } 1102 // Fall through 1103 default: 1104 // Unknown character, bail on formatting 1105 text.replace(0, length, saved); 1106 return; 1107 } 1108 } 1109 1110 if (numDigits == 7) { 1111 // With 7 digits we want xxx-xxxx, not xxx-xxx-x 1112 numDashes--; 1113 } 1114 1115 // Actually put the dashes in place 1116 for (int i = 0; i < numDashes; i++) { 1117 int pos = dashPositions[i]; 1118 text.replace(pos + i, pos + i, "-"); 1119 } 1120 1121 // Remove trailing dashes 1122 int len = text.length(); 1123 while (len > 0) { 1124 if (text.charAt(len - 1) == '-') { 1125 text.delete(len - 1, len); 1126 len--; 1127 } else { 1128 break; 1129 } 1130 } 1131 } 1132 1133 /** 1134 * Formats a phone number in-place using the Japanese formatting rules. 1135 * Numbers will be formatted as: 1136 * 1137 * <p><code> 1138 * 03-xxxx-xxxx 1139 * 090-xxxx-xxxx 1140 * 0120-xxx-xxx 1141 * +81-3-xxxx-xxxx 1142 * +81-90-xxxx-xxxx 1143 * </code></p> 1144 * 1145 * @param text the number to be formatted, will be modified with 1146 * the formatting 1147 */ 1148 public static void formatJapaneseNumber(Editable text) { 1149 JapanesePhoneNumberFormatter.format(text); 1150 } 1151 1152 // Three and four digit phone numbers for either special services 1153 // or from the network (eg carrier-originated SMS messages) should 1154 // not match 1155 static final int MIN_MATCH = 5; 1156 1157 /** 1158 * isEmergencyNumber: checks a given number against the list of 1159 * emergency numbers provided by the RIL and SIM card. 1160 * 1161 * @param number the number to look up. 1162 * @return if the number is in the list of emergency numbers 1163 * listed in the ril / sim, then return true, otherwise false. 1164 */ 1165 public static boolean isEmergencyNumber(String number) { 1166 // Strip the separators from the number before comparing it 1167 // to the list. 1168 number = extractNetworkPortion(number); 1169 1170 // retrieve the list of emergency numbers 1171 String numbers = SystemProperties.get("ro.ril.ecclist"); 1172 1173 if (!TextUtils.isEmpty(numbers)) { 1174 // searches through the comma-separated list for a match, 1175 // return true if one is found. 1176 for (String emergencyNum : numbers.split(",")) { 1177 if (emergencyNum.equals(number)) { 1178 return true; 1179 } 1180 } 1181 // no matches found against the list! 1182 return false; 1183 } 1184 1185 //no ecclist system property, so use our own list. 1186 return (number.equals("112") || number.equals("911")); 1187 } 1188 1189 /** 1190 * Translates any alphabetic letters (i.e. [A-Za-z]) in the 1191 * specified phone number into the equivalent numeric digits, 1192 * according to the phone keypad letter mapping described in 1193 * ITU E.161 and ISO/IEC 9995-8. 1194 * 1195 * @return the input string, with alpha letters converted to numeric 1196 * digits using the phone keypad letter mapping. For example, 1197 * an input of "1-800-GOOG-411" will return "1-800-4664-411". 1198 */ 1199 public static String convertKeypadLettersToDigits(String input) { 1200 if (input == null) { 1201 return input; 1202 } 1203 int len = input.length(); 1204 if (len == 0) { 1205 return input; 1206 } 1207 1208 char[] out = input.toCharArray(); 1209 1210 for (int i = 0; i < len; i++) { 1211 char c = out[i]; 1212 // If this char isn't in KEYPAD_MAP at all, just leave it alone. 1213 out[i] = (char) KEYPAD_MAP.get(c, c); 1214 } 1215 1216 return new String(out); 1217 } 1218 1219 /** 1220 * The phone keypad letter mapping (see ITU E.161 or ISO/IEC 9995-8.) 1221 * TODO: This should come from a resource. 1222 */ 1223 private static final SparseIntArray KEYPAD_MAP = new SparseIntArray(); 1224 static { 1225 KEYPAD_MAP.put('a', '2'); KEYPAD_MAP.put('b', '2'); KEYPAD_MAP.put('c', '2'); 1226 KEYPAD_MAP.put('A', '2'); KEYPAD_MAP.put('B', '2'); KEYPAD_MAP.put('C', '2'); 1227 1228 KEYPAD_MAP.put('d', '3'); KEYPAD_MAP.put('e', '3'); KEYPAD_MAP.put('f', '3'); 1229 KEYPAD_MAP.put('D', '3'); KEYPAD_MAP.put('E', '3'); KEYPAD_MAP.put('F', '3'); 1230 1231 KEYPAD_MAP.put('g', '4'); KEYPAD_MAP.put('h', '4'); KEYPAD_MAP.put('i', '4'); 1232 KEYPAD_MAP.put('G', '4'); KEYPAD_MAP.put('H', '4'); KEYPAD_MAP.put('I', '4'); 1233 1234 KEYPAD_MAP.put('j', '5'); KEYPAD_MAP.put('k', '5'); KEYPAD_MAP.put('l', '5'); 1235 KEYPAD_MAP.put('J', '5'); KEYPAD_MAP.put('K', '5'); KEYPAD_MAP.put('L', '5'); 1236 1237 KEYPAD_MAP.put('m', '6'); KEYPAD_MAP.put('n', '6'); KEYPAD_MAP.put('o', '6'); 1238 KEYPAD_MAP.put('M', '6'); KEYPAD_MAP.put('N', '6'); KEYPAD_MAP.put('O', '6'); 1239 1240 KEYPAD_MAP.put('p', '7'); KEYPAD_MAP.put('q', '7'); KEYPAD_MAP.put('r', '7'); KEYPAD_MAP.put('s', '7'); 1241 KEYPAD_MAP.put('P', '7'); KEYPAD_MAP.put('Q', '7'); KEYPAD_MAP.put('R', '7'); KEYPAD_MAP.put('S', '7'); 1242 1243 KEYPAD_MAP.put('t', '8'); KEYPAD_MAP.put('u', '8'); KEYPAD_MAP.put('v', '8'); 1244 KEYPAD_MAP.put('T', '8'); KEYPAD_MAP.put('U', '8'); KEYPAD_MAP.put('V', '8'); 1245 1246 KEYPAD_MAP.put('w', '9'); KEYPAD_MAP.put('x', '9'); KEYPAD_MAP.put('y', '9'); KEYPAD_MAP.put('z', '9'); 1247 KEYPAD_MAP.put('W', '9'); KEYPAD_MAP.put('X', '9'); KEYPAD_MAP.put('Y', '9'); KEYPAD_MAP.put('Z', '9'); 1248 } 1249 1250 //================ Plus Code formatting ========================= 1251 private static final char PLUS_SIGN_CHAR = '+'; 1252 private static final String PLUS_SIGN_STRING = "+"; 1253 private static final String NANP_IDP_STRING = "011"; 1254 private static final int NANP_LENGTH = 10; 1255 1256 /** 1257 * This function checks if there is a plus sign (+) in the passed-in dialing number. 1258 * If there is, it processes the plus sign based on the default telephone 1259 * numbering plan of the system when the phone is activated and the current 1260 * telephone numbering plan of the system that the phone is camped on. 1261 * Currently, we only support the case that the default and current telephone 1262 * numbering plans are North American Numbering Plan(NANP). 1263 * 1264 * The passed-in dialStr should only contain the valid format as described below, 1265 * 1) the 1st character in the dialStr should be one of the really dialable 1266 * characters listed below 1267 * ISO-LATIN characters 0-9, *, # , + 1268 * 2) the dialStr should already strip out the separator characters, 1269 * every character in the dialStr should be one of the non separator characters 1270 * listed below 1271 * ISO-LATIN characters 0-9, *, # , +, WILD, WAIT, PAUSE 1272 * 1273 * Otherwise, this function returns the dial string passed in 1274 * 1275 * This API is for CDMA only 1276 * 1277 * @hide TODO: pending API Council approval 1278 */ 1279 public static String cdmaCheckAndProcessPlusCode(String dialStr) { 1280 if (!TextUtils.isEmpty(dialStr)) { 1281 if (isReallyDialable(dialStr.charAt(0)) && 1282 isNonSeparator(dialStr)) { 1283 return cdmaCheckAndProcessPlusCodeByNumberFormat(dialStr, 1284 getFormatTypeForLocale(Locale.getDefault())); 1285 } 1286 } 1287 return dialStr; 1288 } 1289 1290 /** 1291 * This function should be called from checkAndProcessPlusCode only 1292 * And it is used for test purpose also. 1293 * 1294 * It checks the dial string by looping through the network portion, 1295 * post dial portion 1, post dial porting 2, etc. If there is any 1296 * plus sign, then process the plus sign. 1297 * Currently, this function supports the plus sign conversion within NANP only. 1298 * Specifically, it handles the plus sign in the following ways: 1299 * 1)+NANP or +1NANP,remove +, e.g. 1300 * +8475797000 is converted to 8475797000, 1301 * +18475797000 is converted to 18475797000, 1302 * 2)+non-NANP Numbers,replace + with the current NANP IDP, e.g, 1303 * +11875767800 is converted to 01111875767800 1304 * 3)+NANP in post dial string(s), e.g. 1305 * 8475797000;+8475231753 is converted to 8475797000;8475231753 1306 * 1307 * This function returns the original dial string if locale/numbering plan 1308 * aren't supported. 1309 * 1310 * @hide 1311 */ 1312 public static String cdmaCheckAndProcessPlusCodeByNumberFormat(String dialStr,int numFormat) { 1313 String retStr = dialStr; 1314 1315 // Checks if the plus sign character is in the passed-in dial string 1316 if (dialStr != null && 1317 dialStr.lastIndexOf(PLUS_SIGN_STRING) != -1) { 1318 1319 String postDialStr = null; 1320 String tempDialStr = dialStr; 1321 1322 // Sets the retStr to null since the conversion will be performed below. 1323 retStr = null; 1324 if (DBG) log("checkAndProcessPlusCode,dialStr=" + dialStr); 1325 // This routine is to process the plus sign in the dial string by loop through 1326 // the network portion, post dial portion 1, post dial portion 2... etc. if 1327 // applied 1328 do { 1329 String networkDialStr; 1330 1331 // Format the string based on the rules for the country the number is from 1332 if (numFormat != FORMAT_NANP) { 1333 // TODO: to support NANP international conversion and 1334 // other telephone numbering plan 1335 // Currently the phone is ever used in non-NANP system 1336 // return the original dial string 1337 Log.e("checkAndProcessPlusCode:non-NANP not supported", dialStr); 1338 return dialStr; 1339 } else { 1340 // For the case that the default and current telephone 1341 // numbering plans are NANP 1342 networkDialStr = extractNetworkPortion(tempDialStr); 1343 // Handles the conversion within NANP 1344 networkDialStr = processPlusCodeWithinNanp(networkDialStr); 1345 } 1346 // Concatenates the string that is converted from network portion 1347 if (!TextUtils.isEmpty(networkDialStr)) { 1348 if (retStr == null) { 1349 retStr = networkDialStr; 1350 } else { 1351 retStr = retStr.concat(networkDialStr); 1352 } 1353 } else { 1354 // This should never happen since we checked the if dialStr is null 1355 // and if it contains the plus sign in the begining of this function. 1356 // The plus sign is part of the network portion. 1357 Log.e("checkAndProcessPlusCode: null newDialStr", networkDialStr); 1358 return dialStr; 1359 } 1360 postDialStr = extractPostDialPortion(tempDialStr); 1361 if (!TextUtils.isEmpty(postDialStr)) { 1362 int dialableIndex = findDialableIndexFromPostDialStr(postDialStr); 1363 1364 // dialableIndex should always be greater than 0 1365 if (dialableIndex >= 1) { 1366 retStr = appendPwCharBackToOrigDialStr(dialableIndex, 1367 retStr,postDialStr); 1368 // Skips the P/W character, extracts the dialable portion 1369 tempDialStr = postDialStr.substring(dialableIndex); 1370 } else { 1371 // Non-dialable character such as P/W should not be at the end of 1372 // the dial string after P/W processing in CdmaConnection.java 1373 // Set the postDialStr to "" to break out of the loop 1374 if (dialableIndex < 0) { 1375 postDialStr = ""; 1376 } 1377 Log.e("wrong postDialStr=", postDialStr); 1378 } 1379 } 1380 if (DBG) log("checkAndProcessPlusCode,postDialStr=" + postDialStr); 1381 } while (!TextUtils.isEmpty(postDialStr) && !TextUtils.isEmpty(tempDialStr)); 1382 } 1383 return retStr; 1384 } 1385 1386 // This function gets the default international dialing prefix 1387 private static String getDefaultIdp( ) { 1388 String ps = null; 1389 SystemProperties.get(PROPERTY_IDP_STRING, ps); 1390 if (TextUtils.isEmpty(ps)) { 1391 ps = NANP_IDP_STRING; 1392 } 1393 return ps; 1394 } 1395 1396 private static boolean isTwoToNine (char c) { 1397 if (c >= '2' && c <= '9') { 1398 return true; 1399 } else { 1400 return false; 1401 } 1402 } 1403 1404 /** 1405 * This function checks if the passed in string conforms to the NANP format 1406 * i.e. NXX-NXX-XXXX, N is any digit 2-9 and X is any digit 0-9 1407 */ 1408 private static boolean isNanp (String dialStr) { 1409 boolean retVal = false; 1410 if (dialStr != null) { 1411 if (dialStr.length() == NANP_LENGTH) { 1412 if (isTwoToNine(dialStr.charAt(0)) && 1413 isTwoToNine(dialStr.charAt(3))) { 1414 retVal = true; 1415 for (int i=1; i<NANP_LENGTH; i++ ) { 1416 char c=dialStr.charAt(i); 1417 if (!PhoneNumberUtils.isISODigit(c)) { 1418 retVal = false; 1419 break; 1420 } 1421 } 1422 } 1423 } 1424 } else { 1425 Log.e("isNanp: null dialStr passed in", dialStr); 1426 } 1427 return retVal; 1428 } 1429 1430 /** 1431 * This function checks if the passed in string conforms to 1-NANP format 1432 */ 1433 private static boolean isOneNanp(String dialStr) { 1434 boolean retVal = false; 1435 if (dialStr != null) { 1436 String newDialStr = dialStr.substring(1); 1437 if ((dialStr.charAt(0) == '1') && isNanp(newDialStr)) { 1438 retVal = true; 1439 } 1440 } else { 1441 Log.e("isOneNanp: null dialStr passed in", dialStr); 1442 } 1443 return retVal; 1444 } 1445 1446 /** 1447 * This function handles the plus code conversion within NANP CDMA network 1448 * If the number format is 1449 * 1)+NANP or +1NANP,remove +, 1450 * 2)+non-NANP Numbers,replace + with the current IDP 1451 */ 1452 private static String processPlusCodeWithinNanp(String networkDialStr) { 1453 String retStr = networkDialStr; 1454 1455 if (DBG) log("processPlusCodeWithinNanp,networkDialStr=" + networkDialStr); 1456 // If there is a plus sign at the beginning of the dial string, 1457 // Convert the plus sign to the default IDP since it's an international number 1458 if (networkDialStr != null & 1459 networkDialStr.charAt(0) == PLUS_SIGN_CHAR && 1460 networkDialStr.length() > 1) { 1461 String newStr = networkDialStr.substring(1); 1462 if (isNanp(newStr) || isOneNanp(newStr)) { 1463 // Remove the leading plus sign 1464 retStr = newStr; 1465 } else { 1466 String idpStr = getDefaultIdp(); 1467 // Replaces the plus sign with the default IDP 1468 retStr = networkDialStr.replaceFirst("[+]", idpStr); 1469 } 1470 } 1471 if (DBG) log("processPlusCodeWithinNanp,retStr=" + retStr); 1472 return retStr; 1473 } 1474 1475 // This function finds the index of the dialable character(s) 1476 // in the post dial string 1477 private static int findDialableIndexFromPostDialStr(String postDialStr) { 1478 for (int index = 0;index < postDialStr.length();index++) { 1479 char c = postDialStr.charAt(index); 1480 if (isReallyDialable(c)) { 1481 return index; 1482 } 1483 } 1484 return -1; 1485 } 1486 1487 // This function appends the non-diablable P/W character to the original 1488 // dial string based on the dialable index passed in 1489 private static String 1490 appendPwCharBackToOrigDialStr(int dialableIndex,String origStr, String dialStr) { 1491 String retStr; 1492 1493 // There is only 1 P/W character before the dialable characters 1494 if (dialableIndex == 1) { 1495 StringBuilder ret = new StringBuilder(origStr); 1496 ret = ret.append(dialStr.charAt(0)); 1497 retStr = ret.toString(); 1498 } else { 1499 // It means more than 1 P/W characters in the post dial string, 1500 // appends to retStr 1501 String nonDigitStr = dialStr.substring(0,dialableIndex); 1502 retStr = origStr.concat(nonDigitStr); 1503 } 1504 return retStr; 1505 } 1506} 1507