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