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