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