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