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.ShortNumberInfo; 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 private static final String JAPAN_ISO_COUNTRY_CODE = "JP"; 1144 1145 /** 1146 * Breaks the given number down and formats it according to the rules 1147 * for the country the number is from. 1148 * 1149 * @param source The phone number to format 1150 * @return A locally acceptable formatting of the input, or the raw input if 1151 * formatting rules aren't known for the number 1152 * 1153 * @deprecated Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead 1154 */ 1155 @Deprecated formatNumber(String source)1156 public static String formatNumber(String source) { 1157 SpannableStringBuilder text = new SpannableStringBuilder(source); 1158 formatNumber(text, getFormatTypeForLocale(Locale.getDefault())); 1159 return text.toString(); 1160 } 1161 1162 /** 1163 * Formats the given number with the given formatting type. Currently 1164 * {@link #FORMAT_NANP} and {@link #FORMAT_JAPAN} are supported as a formating type. 1165 * 1166 * @param source the phone number to format 1167 * @param defaultFormattingType The default formatting rules to apply if the number does 1168 * not begin with +[country_code] 1169 * @return The phone number formatted with the given formatting type. 1170 * 1171 * @hide 1172 * @deprecated Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead 1173 */ 1174 @Deprecated formatNumber(String source, int defaultFormattingType)1175 public static String formatNumber(String source, int defaultFormattingType) { 1176 SpannableStringBuilder text = new SpannableStringBuilder(source); 1177 formatNumber(text, defaultFormattingType); 1178 return text.toString(); 1179 } 1180 1181 /** 1182 * Returns the phone number formatting type for the given locale. 1183 * 1184 * @param locale The locale of interest, usually {@link Locale#getDefault()} 1185 * @return The formatting type for the given locale, or FORMAT_UNKNOWN if the formatting 1186 * rules are not known for the given locale 1187 * 1188 * @deprecated Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead 1189 */ 1190 @Deprecated getFormatTypeForLocale(Locale locale)1191 public static int getFormatTypeForLocale(Locale locale) { 1192 String country = locale.getCountry(); 1193 1194 return getFormatTypeFromCountryCode(country); 1195 } 1196 1197 /** 1198 * Formats a phone number in-place. Currently {@link #FORMAT_JAPAN} and {@link #FORMAT_NANP} 1199 * is supported as a second argument. 1200 * 1201 * @param text The number to be formatted, will be modified with the formatting 1202 * @param defaultFormattingType The default formatting rules to apply if the number does 1203 * not begin with +[country_code] 1204 * 1205 * @deprecated Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead 1206 */ 1207 @Deprecated formatNumber(Editable text, int defaultFormattingType)1208 public static void formatNumber(Editable text, int defaultFormattingType) { 1209 int formatType = defaultFormattingType; 1210 1211 if (text.length() > 2 && text.charAt(0) == '+') { 1212 if (text.charAt(1) == '1') { 1213 formatType = FORMAT_NANP; 1214 } else if (text.length() >= 3 && text.charAt(1) == '8' 1215 && text.charAt(2) == '1') { 1216 formatType = FORMAT_JAPAN; 1217 } else { 1218 formatType = FORMAT_UNKNOWN; 1219 } 1220 } 1221 1222 switch (formatType) { 1223 case FORMAT_NANP: 1224 formatNanpNumber(text); 1225 return; 1226 case FORMAT_JAPAN: 1227 formatJapaneseNumber(text); 1228 return; 1229 case FORMAT_UNKNOWN: 1230 removeDashes(text); 1231 return; 1232 } 1233 } 1234 1235 private static final int NANP_STATE_DIGIT = 1; 1236 private static final int NANP_STATE_PLUS = 2; 1237 private static final int NANP_STATE_ONE = 3; 1238 private static final int NANP_STATE_DASH = 4; 1239 1240 /** 1241 * Formats a phone number in-place using the NANP formatting rules. Numbers will be formatted 1242 * as: 1243 * 1244 * <p><code> 1245 * xxxxx 1246 * xxx-xxxx 1247 * xxx-xxx-xxxx 1248 * 1-xxx-xxx-xxxx 1249 * +1-xxx-xxx-xxxx 1250 * </code></p> 1251 * 1252 * @param text the number to be formatted, will be modified with the formatting 1253 * 1254 * @deprecated Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead 1255 */ 1256 @Deprecated formatNanpNumber(Editable text)1257 public static void formatNanpNumber(Editable text) { 1258 int length = text.length(); 1259 if (length > "+1-nnn-nnn-nnnn".length()) { 1260 // The string is too long to be formatted 1261 return; 1262 } else if (length <= 5) { 1263 // The string is either a shortcode or too short to be formatted 1264 return; 1265 } 1266 1267 CharSequence saved = text.subSequence(0, length); 1268 1269 // Strip the dashes first, as we're going to add them back 1270 removeDashes(text); 1271 length = text.length(); 1272 1273 // When scanning the number we record where dashes need to be added, 1274 // if they're non-0 at the end of the scan the dashes will be added in 1275 // the proper places. 1276 int dashPositions[] = new int[3]; 1277 int numDashes = 0; 1278 1279 int state = NANP_STATE_DIGIT; 1280 int numDigits = 0; 1281 for (int i = 0; i < length; i++) { 1282 char c = text.charAt(i); 1283 switch (c) { 1284 case '1': 1285 if (numDigits == 0 || state == NANP_STATE_PLUS) { 1286 state = NANP_STATE_ONE; 1287 break; 1288 } 1289 // fall through 1290 case '2': 1291 case '3': 1292 case '4': 1293 case '5': 1294 case '6': 1295 case '7': 1296 case '8': 1297 case '9': 1298 case '0': 1299 if (state == NANP_STATE_PLUS) { 1300 // Only NANP number supported for now 1301 text.replace(0, length, saved); 1302 return; 1303 } else if (state == NANP_STATE_ONE) { 1304 // Found either +1 or 1, follow it up with a dash 1305 dashPositions[numDashes++] = i; 1306 } else if (state != NANP_STATE_DASH && (numDigits == 3 || numDigits == 6)) { 1307 // Found a digit that should be after a dash that isn't 1308 dashPositions[numDashes++] = i; 1309 } 1310 state = NANP_STATE_DIGIT; 1311 numDigits++; 1312 break; 1313 1314 case '-': 1315 state = NANP_STATE_DASH; 1316 break; 1317 1318 case '+': 1319 if (i == 0) { 1320 // Plus is only allowed as the first character 1321 state = NANP_STATE_PLUS; 1322 break; 1323 } 1324 // Fall through 1325 default: 1326 // Unknown character, bail on formatting 1327 text.replace(0, length, saved); 1328 return; 1329 } 1330 } 1331 1332 if (numDigits == 7) { 1333 // With 7 digits we want xxx-xxxx, not xxx-xxx-x 1334 numDashes--; 1335 } 1336 1337 // Actually put the dashes in place 1338 for (int i = 0; i < numDashes; i++) { 1339 int pos = dashPositions[i]; 1340 text.replace(pos + i, pos + i, "-"); 1341 } 1342 1343 // Remove trailing dashes 1344 int len = text.length(); 1345 while (len > 0) { 1346 if (text.charAt(len - 1) == '-') { 1347 text.delete(len - 1, len); 1348 len--; 1349 } else { 1350 break; 1351 } 1352 } 1353 } 1354 1355 /** 1356 * Formats a phone number in-place using the Japanese formatting rules. 1357 * Numbers will be formatted as: 1358 * 1359 * <p><code> 1360 * 03-xxxx-xxxx 1361 * 090-xxxx-xxxx 1362 * 0120-xxx-xxx 1363 * +81-3-xxxx-xxxx 1364 * +81-90-xxxx-xxxx 1365 * </code></p> 1366 * 1367 * @param text the number to be formatted, will be modified with 1368 * the formatting 1369 * 1370 * @deprecated Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead 1371 */ 1372 @Deprecated formatJapaneseNumber(Editable text)1373 public static void formatJapaneseNumber(Editable text) { 1374 JapanesePhoneNumberFormatter.format(text); 1375 } 1376 1377 /** 1378 * Removes all dashes from the number. 1379 * 1380 * @param text the number to clear from dashes 1381 */ removeDashes(Editable text)1382 private static void removeDashes(Editable text) { 1383 int p = 0; 1384 while (p < text.length()) { 1385 if (text.charAt(p) == '-') { 1386 text.delete(p, p + 1); 1387 } else { 1388 p++; 1389 } 1390 } 1391 } 1392 1393 /** 1394 * Formats the specified {@code phoneNumber} to the E.164 representation. 1395 * 1396 * @param phoneNumber the phone number to format. 1397 * @param defaultCountryIso the ISO 3166-1 two letters country code. 1398 * @return the E.164 representation, or null if the given phone number is not valid. 1399 */ formatNumberToE164(String phoneNumber, String defaultCountryIso)1400 public static String formatNumberToE164(String phoneNumber, String defaultCountryIso) { 1401 return formatNumberInternal(phoneNumber, defaultCountryIso, PhoneNumberFormat.E164); 1402 } 1403 1404 /** 1405 * Formats the specified {@code phoneNumber} to the RFC3966 representation. 1406 * 1407 * @param phoneNumber the phone number to format. 1408 * @param defaultCountryIso the ISO 3166-1 two letters country code. 1409 * @return the RFC3966 representation, or null if the given phone number is not valid. 1410 */ formatNumberToRFC3966(String phoneNumber, String defaultCountryIso)1411 public static String formatNumberToRFC3966(String phoneNumber, String defaultCountryIso) { 1412 return formatNumberInternal(phoneNumber, defaultCountryIso, PhoneNumberFormat.RFC3966); 1413 } 1414 1415 /** 1416 * Formats the raw phone number (string) using the specified {@code formatIdentifier}. 1417 * <p> 1418 * The given phone number must have an area code and could have a country code. 1419 * <p> 1420 * The defaultCountryIso is used to validate the given number and generate the formatted number 1421 * if the specified number doesn't have a country code. 1422 * 1423 * @param rawPhoneNumber The phone number to format. 1424 * @param defaultCountryIso The ISO 3166-1 two letters country code. 1425 * @param formatIdentifier The (enum) identifier of the desired format. 1426 * @return the formatted representation, or null if the specified number is not valid. 1427 */ formatNumberInternal( String rawPhoneNumber, String defaultCountryIso, PhoneNumberFormat formatIdentifier)1428 private static String formatNumberInternal( 1429 String rawPhoneNumber, String defaultCountryIso, PhoneNumberFormat formatIdentifier) { 1430 1431 PhoneNumberUtil util = PhoneNumberUtil.getInstance(); 1432 try { 1433 PhoneNumber phoneNumber = util.parse(rawPhoneNumber, defaultCountryIso); 1434 if (util.isValidNumber(phoneNumber)) { 1435 return util.format(phoneNumber, formatIdentifier); 1436 } 1437 } catch (NumberParseException ignored) { } 1438 1439 return null; 1440 } 1441 1442 /** 1443 * Determines if a {@param phoneNumber} is international if dialed from 1444 * {@param defaultCountryIso}. 1445 * 1446 * @param phoneNumber The phone number. 1447 * @param defaultCountryIso The current country ISO. 1448 * @return {@code true} if the number is international, {@code false} otherwise. 1449 * @hide 1450 */ isInternationalNumber(String phoneNumber, String defaultCountryIso)1451 public static boolean isInternationalNumber(String phoneNumber, String defaultCountryIso) { 1452 // If no phone number is provided, it can't be international. 1453 if (TextUtils.isEmpty(phoneNumber)) { 1454 return false; 1455 } 1456 1457 // If it starts with # or * its not international. 1458 if (phoneNumber.startsWith("#") || phoneNumber.startsWith("*")) { 1459 return false; 1460 } 1461 1462 PhoneNumberUtil util = PhoneNumberUtil.getInstance(); 1463 try { 1464 PhoneNumber pn = util.parseAndKeepRawInput(phoneNumber, defaultCountryIso); 1465 return pn.getCountryCode() != util.getCountryCodeForRegion(defaultCountryIso); 1466 } catch (NumberParseException e) { 1467 return false; 1468 } 1469 } 1470 1471 /** 1472 * Format a phone number. 1473 * <p> 1474 * If the given number doesn't have the country code, the phone will be 1475 * formatted to the default country's convention. 1476 * 1477 * @param phoneNumber 1478 * the number to be formatted. 1479 * @param defaultCountryIso 1480 * the ISO 3166-1 two letters country code whose convention will 1481 * be used if the given number doesn't have the country code. 1482 * @return the formatted number, or null if the given number is not valid. 1483 */ formatNumber(String phoneNumber, String defaultCountryIso)1484 public static String formatNumber(String phoneNumber, String defaultCountryIso) { 1485 // Do not attempt to format numbers that start with a hash or star symbol. 1486 if (phoneNumber.startsWith("#") || phoneNumber.startsWith("*")) { 1487 return phoneNumber; 1488 } 1489 1490 PhoneNumberUtil util = PhoneNumberUtil.getInstance(); 1491 String result = null; 1492 try { 1493 PhoneNumber pn = util.parseAndKeepRawInput(phoneNumber, defaultCountryIso); 1494 1495 if (KOREA_ISO_COUNTRY_CODE.equalsIgnoreCase(defaultCountryIso) && 1496 (pn.getCountryCode() == util.getCountryCodeForRegion(KOREA_ISO_COUNTRY_CODE)) && 1497 (pn.getCountryCodeSource() == 1498 PhoneNumber.CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN)) { 1499 /** 1500 * Need to reformat any local Korean phone numbers (when the user is in Korea) with 1501 * country code to corresponding national format which would replace the leading 1502 * +82 with 0. 1503 */ 1504 result = util.format(pn, PhoneNumberUtil.PhoneNumberFormat.NATIONAL); 1505 } else if (JAPAN_ISO_COUNTRY_CODE.equalsIgnoreCase(defaultCountryIso) && 1506 pn.getCountryCode() == util.getCountryCodeForRegion(JAPAN_ISO_COUNTRY_CODE) && 1507 (pn.getCountryCodeSource() == 1508 PhoneNumber.CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN)) { 1509 /** 1510 * Need to reformat Japanese phone numbers (when user is in Japan) with the national 1511 * dialing format. 1512 */ 1513 result = util.format(pn, PhoneNumberUtil.PhoneNumberFormat.NATIONAL); 1514 } else { 1515 result = util.formatInOriginalFormat(pn, defaultCountryIso); 1516 } 1517 } catch (NumberParseException e) { 1518 } 1519 return result; 1520 } 1521 1522 /** 1523 * Format the phone number only if the given number hasn't been formatted. 1524 * <p> 1525 * The number which has only dailable character is treated as not being 1526 * formatted. 1527 * 1528 * @param phoneNumber 1529 * the number to be formatted. 1530 * @param phoneNumberE164 1531 * the E164 format number whose country code is used if the given 1532 * phoneNumber doesn't have the country code. 1533 * @param defaultCountryIso 1534 * the ISO 3166-1 two letters country code whose convention will 1535 * be used if the phoneNumberE164 is null or invalid, or if phoneNumber 1536 * contains IDD. 1537 * @return the formatted number if the given number has been formatted, 1538 * otherwise, return the given number. 1539 */ formatNumber( String phoneNumber, String phoneNumberE164, String defaultCountryIso)1540 public static String formatNumber( 1541 String phoneNumber, String phoneNumberE164, String defaultCountryIso) { 1542 int len = phoneNumber.length(); 1543 for (int i = 0; i < len; i++) { 1544 if (!isDialable(phoneNumber.charAt(i))) { 1545 return phoneNumber; 1546 } 1547 } 1548 PhoneNumberUtil util = PhoneNumberUtil.getInstance(); 1549 // Get the country code from phoneNumberE164 1550 if (phoneNumberE164 != null && phoneNumberE164.length() >= 2 1551 && phoneNumberE164.charAt(0) == '+') { 1552 try { 1553 // The number to be parsed is in E164 format, so the default region used doesn't 1554 // matter. 1555 PhoneNumber pn = util.parse(phoneNumberE164, "ZZ"); 1556 String regionCode = util.getRegionCodeForNumber(pn); 1557 if (!TextUtils.isEmpty(regionCode) && 1558 // This makes sure phoneNumber doesn't contain an IDD 1559 normalizeNumber(phoneNumber).indexOf(phoneNumberE164.substring(1)) <= 0) { 1560 defaultCountryIso = regionCode; 1561 } 1562 } catch (NumberParseException e) { 1563 } 1564 } 1565 String result = formatNumber(phoneNumber, defaultCountryIso); 1566 return result != null ? result : phoneNumber; 1567 } 1568 1569 /** 1570 * Normalize a phone number by removing the characters other than digits. If 1571 * the given number has keypad letters, the letters will be converted to 1572 * digits first. 1573 * 1574 * @param phoneNumber the number to be normalized. 1575 * @return the normalized number. 1576 */ normalizeNumber(String phoneNumber)1577 public static String normalizeNumber(String phoneNumber) { 1578 if (TextUtils.isEmpty(phoneNumber)) { 1579 return ""; 1580 } 1581 1582 StringBuilder sb = new StringBuilder(); 1583 int len = phoneNumber.length(); 1584 for (int i = 0; i < len; i++) { 1585 char c = phoneNumber.charAt(i); 1586 // Character.digit() supports ASCII and Unicode digits (fullwidth, Arabic-Indic, etc.) 1587 int digit = Character.digit(c, 10); 1588 if (digit != -1) { 1589 sb.append(digit); 1590 } else if (sb.length() == 0 && c == '+') { 1591 sb.append(c); 1592 } else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { 1593 return normalizeNumber(PhoneNumberUtils.convertKeypadLettersToDigits(phoneNumber)); 1594 } 1595 } 1596 return sb.toString(); 1597 } 1598 1599 /** 1600 * Replaces all unicode(e.g. Arabic, Persian) digits with their decimal digit equivalents. 1601 * 1602 * @param number the number to perform the replacement on. 1603 * @return the replaced number. 1604 */ replaceUnicodeDigits(String number)1605 public static String replaceUnicodeDigits(String number) { 1606 StringBuilder normalizedDigits = new StringBuilder(number.length()); 1607 for (char c : number.toCharArray()) { 1608 int digit = Character.digit(c, 10); 1609 if (digit != -1) { 1610 normalizedDigits.append(digit); 1611 } else { 1612 normalizedDigits.append(c); 1613 } 1614 } 1615 return normalizedDigits.toString(); 1616 } 1617 1618 // Three and four digit phone numbers for either special services, 1619 // or 3-6 digit addresses from the network (eg carrier-originated SMS messages) should 1620 // not match. 1621 // 1622 // This constant used to be 5, but SMS short codes has increased in length and 1623 // can be easily 6 digits now days. Most countries have SMS short code length between 1624 // 3 to 6 digits. The exceptions are 1625 // 1626 // Australia: Short codes are six or eight digits in length, starting with the prefix "19" 1627 // followed by an additional four or six digits and two. 1628 // Czechia: Codes are seven digits in length for MO and five (not billed) or 1629 // eight (billed) for MT direction 1630 // 1631 // see http://en.wikipedia.org/wiki/Short_code#Regional_differences for reference 1632 // 1633 // However, in order to loose match 650-555-1212 and 555-1212, we need to set the min match 1634 // to 7. 1635 static final int MIN_MATCH = 7; 1636 1637 /** 1638 * Checks a given number against the list of 1639 * emergency numbers provided by the RIL and SIM card. 1640 * 1641 * @param number the number to look up. 1642 * @return true if the number is in the list of emergency numbers 1643 * listed in the RIL / SIM, otherwise return false. 1644 */ isEmergencyNumber(String number)1645 public static boolean isEmergencyNumber(String number) { 1646 return isEmergencyNumber(getDefaultVoiceSubId(), number); 1647 } 1648 1649 /** 1650 * Checks a given number against the list of 1651 * emergency numbers provided by the RIL and SIM card. 1652 * 1653 * @param subId the subscription id of the SIM. 1654 * @param number the number to look up. 1655 * @return true if the number is in the list of emergency numbers 1656 * listed in the RIL / SIM, otherwise return false. 1657 * @hide 1658 */ isEmergencyNumber(int subId, String number)1659 public static boolean isEmergencyNumber(int subId, String number) { 1660 // Return true only if the specified number *exactly* matches 1661 // one of the emergency numbers listed by the RIL / SIM. 1662 return isEmergencyNumberInternal(subId, number, true /* useExactMatch */); 1663 } 1664 1665 /** 1666 * Checks if given number might *potentially* result in 1667 * a call to an emergency service on the current network. 1668 * 1669 * Specifically, this method will return true if the specified number 1670 * is an emergency number according to the list managed by the RIL or 1671 * SIM, *or* if the specified number simply starts with the same 1672 * digits as any of the emergency numbers listed in the RIL / SIM. 1673 * 1674 * This method is intended for internal use by the phone app when 1675 * deciding whether to allow ACTION_CALL intents from 3rd party apps 1676 * (where we're required to *not* allow emergency calls to be placed.) 1677 * 1678 * @param number the number to look up. 1679 * @return true if the number is in the list of emergency numbers 1680 * listed in the RIL / SIM, *or* if the number starts with the 1681 * same digits as any of those emergency numbers. 1682 * 1683 * @hide 1684 */ isPotentialEmergencyNumber(String number)1685 public static boolean isPotentialEmergencyNumber(String number) { 1686 return isPotentialEmergencyNumber(getDefaultVoiceSubId(), number); 1687 } 1688 1689 /** 1690 * Checks if given number might *potentially* result in 1691 * a call to an emergency service on the current network. 1692 * 1693 * Specifically, this method will return true if the specified number 1694 * is an emergency number according to the list managed by the RIL or 1695 * SIM, *or* if the specified number simply starts with the same 1696 * digits as any of the emergency numbers listed in the RIL / SIM. 1697 * 1698 * This method is intended for internal use by the phone app when 1699 * deciding whether to allow ACTION_CALL intents from 3rd party apps 1700 * (where we're required to *not* allow emergency calls to be placed.) 1701 * 1702 * @param subId the subscription id of the SIM. 1703 * @param number the number to look up. 1704 * @return true if the number is in the list of emergency numbers 1705 * listed in the RIL / SIM, *or* if the number starts with the 1706 * same digits as any of those emergency numbers. 1707 * @hide 1708 */ isPotentialEmergencyNumber(int subId, String number)1709 public static boolean isPotentialEmergencyNumber(int subId, String number) { 1710 // Check against the emergency numbers listed by the RIL / SIM, 1711 // and *don't* require an exact match. 1712 return isEmergencyNumberInternal(subId, number, false /* useExactMatch */); 1713 } 1714 1715 /** 1716 * Helper function for isEmergencyNumber(String) and 1717 * isPotentialEmergencyNumber(String). 1718 * 1719 * @param number the number to look up. 1720 * 1721 * @param useExactMatch if true, consider a number to be an emergency 1722 * number only if it *exactly* matches a number listed in 1723 * the RIL / SIM. If false, a number is considered to be an 1724 * emergency number if it simply starts with the same digits 1725 * as any of the emergency numbers listed in the RIL / SIM. 1726 * (Setting useExactMatch to false allows you to identify 1727 * number that could *potentially* result in emergency calls 1728 * since many networks will actually ignore trailing digits 1729 * after a valid emergency number.) 1730 * 1731 * @return true if the number is in the list of emergency numbers 1732 * listed in the RIL / sim, otherwise return false. 1733 */ isEmergencyNumberInternal(String number, boolean useExactMatch)1734 private static boolean isEmergencyNumberInternal(String number, boolean useExactMatch) { 1735 return isEmergencyNumberInternal(getDefaultVoiceSubId(), number, useExactMatch); 1736 } 1737 1738 /** 1739 * Helper function for isEmergencyNumber(String) and 1740 * isPotentialEmergencyNumber(String). 1741 * 1742 * @param subId the subscription id of the SIM. 1743 * @param number the number to look up. 1744 * 1745 * @param useExactMatch if true, consider a number to be an emergency 1746 * number only if it *exactly* matches a number listed in 1747 * the RIL / SIM. If false, a number is considered to be an 1748 * emergency number if it simply starts with the same digits 1749 * as any of the emergency numbers listed in the RIL / SIM. 1750 * (Setting useExactMatch to false allows you to identify 1751 * number that could *potentially* result in emergency calls 1752 * since many networks will actually ignore trailing digits 1753 * after a valid emergency number.) 1754 * 1755 * @return true if the number is in the list of emergency numbers 1756 * listed in the RIL / sim, otherwise return false. 1757 */ isEmergencyNumberInternal(int subId, String number, boolean useExactMatch)1758 private static boolean isEmergencyNumberInternal(int subId, String number, 1759 boolean useExactMatch) { 1760 return isEmergencyNumberInternal(subId, number, null, useExactMatch); 1761 } 1762 1763 /** 1764 * Checks if a given number is an emergency number for a specific country. 1765 * 1766 * @param number the number to look up. 1767 * @param defaultCountryIso the specific country which the number should be checked against 1768 * @return if the number is an emergency number for the specific country, then return true, 1769 * otherwise false 1770 * 1771 * @hide 1772 */ isEmergencyNumber(String number, String defaultCountryIso)1773 public static boolean isEmergencyNumber(String number, String defaultCountryIso) { 1774 return isEmergencyNumber(getDefaultVoiceSubId(), number, defaultCountryIso); 1775 } 1776 1777 /** 1778 * Checks if a given number is an emergency number for a specific country. 1779 * 1780 * @param subId the subscription id of the SIM. 1781 * @param number the number to look up. 1782 * @param defaultCountryIso the specific country which the number should be checked against 1783 * @return if the number is an emergency number for the specific country, then return true, 1784 * otherwise false 1785 * @hide 1786 */ isEmergencyNumber(int subId, String number, String defaultCountryIso)1787 public static boolean isEmergencyNumber(int subId, String number, String defaultCountryIso) { 1788 return isEmergencyNumberInternal(subId, number, 1789 defaultCountryIso, 1790 true /* useExactMatch */); 1791 } 1792 1793 /** 1794 * Checks if a given number might *potentially* result in a call to an 1795 * emergency service, for a specific country. 1796 * 1797 * Specifically, this method will return true if the specified number 1798 * is an emergency number in the specified country, *or* if the number 1799 * simply starts with the same digits as any emergency number for that 1800 * country. 1801 * 1802 * This method is intended for internal use by the phone app when 1803 * deciding whether to allow ACTION_CALL intents from 3rd party apps 1804 * (where we're required to *not* allow emergency calls to be placed.) 1805 * 1806 * @param number the number to look up. 1807 * @param defaultCountryIso the specific country which the number should be checked against 1808 * @return true if the number is an emergency number for the specific 1809 * country, *or* if the number starts with the same digits as 1810 * any of those emergency numbers. 1811 * 1812 * @hide 1813 */ isPotentialEmergencyNumber(String number, String defaultCountryIso)1814 public static boolean isPotentialEmergencyNumber(String number, String defaultCountryIso) { 1815 return isPotentialEmergencyNumber(getDefaultVoiceSubId(), number, defaultCountryIso); 1816 } 1817 1818 /** 1819 * Checks if a given number might *potentially* result in a call to an 1820 * emergency service, for a specific country. 1821 * 1822 * Specifically, this method will return true if the specified number 1823 * is an emergency number in the specified country, *or* if the number 1824 * simply starts with the same digits as any emergency number for that 1825 * country. 1826 * 1827 * This method is intended for internal use by the phone app when 1828 * deciding whether to allow ACTION_CALL intents from 3rd party apps 1829 * (where we're required to *not* allow emergency calls to be placed.) 1830 * 1831 * @param subId the subscription id of the SIM. 1832 * @param number the number to look up. 1833 * @param defaultCountryIso the specific country which the number should be checked against 1834 * @return true if the number is an emergency number for the specific 1835 * country, *or* if the number starts with the same digits as 1836 * any of those emergency numbers. 1837 * @hide 1838 */ isPotentialEmergencyNumber(int subId, String number, String defaultCountryIso)1839 public static boolean isPotentialEmergencyNumber(int subId, String number, 1840 String defaultCountryIso) { 1841 return isEmergencyNumberInternal(subId, number, 1842 defaultCountryIso, 1843 false /* useExactMatch */); 1844 } 1845 1846 /** 1847 * Helper function for isEmergencyNumber(String, String) and 1848 * isPotentialEmergencyNumber(String, String). 1849 * 1850 * @param number the number to look up. 1851 * @param defaultCountryIso the specific country which the number should be checked against 1852 * @param useExactMatch if true, consider a number to be an emergency 1853 * number only if it *exactly* matches a number listed in 1854 * the RIL / SIM. If false, a number is considered to be an 1855 * emergency number if it simply starts with the same digits 1856 * as any of the emergency numbers listed in the RIL / SIM. 1857 * 1858 * @return true if the number is an emergency number for the specified country. 1859 */ isEmergencyNumberInternal(String number, String defaultCountryIso, boolean useExactMatch)1860 private static boolean isEmergencyNumberInternal(String number, 1861 String defaultCountryIso, 1862 boolean useExactMatch) { 1863 return isEmergencyNumberInternal(getDefaultVoiceSubId(), number, defaultCountryIso, 1864 useExactMatch); 1865 } 1866 1867 /** 1868 * Helper function for isEmergencyNumber(String, String) and 1869 * isPotentialEmergencyNumber(String, String). 1870 * 1871 * @param subId the subscription id of the SIM. 1872 * @param number the number to look up. 1873 * @param defaultCountryIso the specific country which the number should be checked against 1874 * @param useExactMatch if true, consider a number to be an emergency 1875 * number only if it *exactly* matches a number listed in 1876 * the RIL / SIM. If false, a number is considered to be an 1877 * emergency number if it simply starts with the same digits 1878 * as any of the emergency numbers listed in the RIL / SIM. 1879 * 1880 * @return true if the number is an emergency number for the specified country. 1881 * @hide 1882 */ isEmergencyNumberInternal(int subId, String number, String defaultCountryIso, boolean useExactMatch)1883 private static boolean isEmergencyNumberInternal(int subId, String number, 1884 String defaultCountryIso, 1885 boolean useExactMatch) { 1886 // If the number passed in is null, just return false: 1887 if (number == null) return false; 1888 1889 // If the number passed in is a SIP address, return false, since the 1890 // concept of "emergency numbers" is only meaningful for calls placed 1891 // over the cell network. 1892 // (Be sure to do this check *before* calling extractNetworkPortionAlt(), 1893 // since the whole point of extractNetworkPortionAlt() is to filter out 1894 // any non-dialable characters (which would turn 'abc911def@example.com' 1895 // into '911', for example.)) 1896 if (isUriNumber(number)) { 1897 return false; 1898 } 1899 1900 // Strip the separators from the number before comparing it 1901 // to the list. 1902 number = extractNetworkPortionAlt(number); 1903 1904 String emergencyNumbers = ""; 1905 int slotId = SubscriptionManager.getSlotIndex(subId); 1906 1907 // retrieve the list of emergency numbers 1908 // check read-write ecclist property first 1909 String ecclist = (slotId <= 0) ? "ril.ecclist" : ("ril.ecclist" + slotId); 1910 1911 emergencyNumbers = SystemProperties.get(ecclist, ""); 1912 1913 Rlog.d(LOG_TAG, "slotId:" + slotId + " subId:" + subId + " country:" 1914 + defaultCountryIso + " emergencyNumbers: " + emergencyNumbers); 1915 1916 if (TextUtils.isEmpty(emergencyNumbers)) { 1917 // then read-only ecclist property since old RIL only uses this 1918 emergencyNumbers = SystemProperties.get("ro.ril.ecclist"); 1919 } 1920 1921 if (!TextUtils.isEmpty(emergencyNumbers)) { 1922 // searches through the comma-separated list for a match, 1923 // return true if one is found. 1924 for (String emergencyNum : emergencyNumbers.split(",")) { 1925 // It is not possible to append additional digits to an emergency number to dial 1926 // the number in Brazil - it won't connect. 1927 if (useExactMatch || "BR".equalsIgnoreCase(defaultCountryIso)) { 1928 if (number.equals(emergencyNum)) { 1929 return true; 1930 } 1931 } else { 1932 if (number.startsWith(emergencyNum)) { 1933 return true; 1934 } 1935 } 1936 } 1937 // no matches found against the list! 1938 return false; 1939 } 1940 1941 Rlog.d(LOG_TAG, "System property doesn't provide any emergency numbers." 1942 + " Use embedded logic for determining ones."); 1943 1944 // If slot id is invalid, means that there is no sim card. 1945 // According spec 3GPP TS22.101, the following numbers should be 1946 // ECC numbers when SIM/USIM is not present. 1947 emergencyNumbers = ((slotId < 0) ? "112,911,000,08,110,118,119,999" : "112,911"); 1948 1949 for (String emergencyNum : emergencyNumbers.split(",")) { 1950 if (useExactMatch) { 1951 if (number.equals(emergencyNum)) { 1952 return true; 1953 } 1954 } else { 1955 if (number.startsWith(emergencyNum)) { 1956 return true; 1957 } 1958 } 1959 } 1960 1961 // No ecclist system property, so use our own list. 1962 if (defaultCountryIso != null) { 1963 ShortNumberInfo info = ShortNumberInfo.getInstance(); 1964 if (useExactMatch) { 1965 return info.isEmergencyNumber(number, defaultCountryIso); 1966 } else { 1967 return info.connectsToEmergencyNumber(number, defaultCountryIso); 1968 } 1969 } 1970 1971 return false; 1972 } 1973 1974 /** 1975 * Checks if a given number is an emergency number for the country that the user is in. 1976 * 1977 * @param number the number to look up. 1978 * @param context the specific context which the number should be checked against 1979 * @return true if the specified number is an emergency number for the country the user 1980 * is currently in. 1981 */ isLocalEmergencyNumber(Context context, String number)1982 public static boolean isLocalEmergencyNumber(Context context, String number) { 1983 return isLocalEmergencyNumber(context, getDefaultVoiceSubId(), number); 1984 } 1985 1986 /** 1987 * Checks if a given number is an emergency number for the country that the user is in. 1988 * 1989 * @param subId the subscription id of the SIM. 1990 * @param number the number to look up. 1991 * @param context the specific context which the number should be checked against 1992 * @return true if the specified number is an emergency number for the country the user 1993 * is currently in. 1994 * @hide 1995 */ isLocalEmergencyNumber(Context context, int subId, String number)1996 public static boolean isLocalEmergencyNumber(Context context, int subId, String number) { 1997 return isLocalEmergencyNumberInternal(subId, number, 1998 context, 1999 true /* useExactMatch */); 2000 } 2001 2002 /** 2003 * Checks if a given number might *potentially* result in a call to an 2004 * emergency service, for the country that the user is in. The current 2005 * country is determined using the CountryDetector. 2006 * 2007 * Specifically, this method will return true if the specified number 2008 * is an emergency number in the current country, *or* if the number 2009 * simply starts with the same digits as any emergency number for the 2010 * current country. 2011 * 2012 * This method is intended for internal use by the phone app when 2013 * deciding whether to allow ACTION_CALL intents from 3rd party apps 2014 * (where we're required to *not* allow emergency calls to be placed.) 2015 * 2016 * @param number the number to look up. 2017 * @param context the specific context which the number should be checked against 2018 * @return true if the specified number is an emergency number for a local country, based on the 2019 * CountryDetector. 2020 * 2021 * @see android.location.CountryDetector 2022 * @hide 2023 */ isPotentialLocalEmergencyNumber(Context context, String number)2024 public static boolean isPotentialLocalEmergencyNumber(Context context, String number) { 2025 return isPotentialLocalEmergencyNumber(context, getDefaultVoiceSubId(), number); 2026 } 2027 2028 /** 2029 * Checks if a given number might *potentially* result in a call to an 2030 * emergency service, for the country that the user is in. The current 2031 * country is determined using the CountryDetector. 2032 * 2033 * Specifically, this method will return true if the specified number 2034 * is an emergency number in the current country, *or* if the number 2035 * simply starts with the same digits as any emergency number for the 2036 * current country. 2037 * 2038 * This method is intended for internal use by the phone app when 2039 * deciding whether to allow ACTION_CALL intents from 3rd party apps 2040 * (where we're required to *not* allow emergency calls to be placed.) 2041 * 2042 * @param subId the subscription id of the SIM. 2043 * @param number the number to look up. 2044 * @param context the specific context which the number should be checked against 2045 * @return true if the specified number is an emergency number for a local country, based on the 2046 * CountryDetector. 2047 * 2048 * @hide 2049 */ isPotentialLocalEmergencyNumber(Context context, int subId, String number)2050 public static boolean isPotentialLocalEmergencyNumber(Context context, int subId, 2051 String number) { 2052 return isLocalEmergencyNumberInternal(subId, number, 2053 context, 2054 false /* useExactMatch */); 2055 } 2056 2057 /** 2058 * Helper function for isLocalEmergencyNumber() and 2059 * isPotentialLocalEmergencyNumber(). 2060 * 2061 * @param number the number to look up. 2062 * @param context the specific context which the number should be checked against 2063 * @param useExactMatch if true, consider a number to be an emergency 2064 * number only if it *exactly* matches a number listed in 2065 * the RIL / SIM. If false, a number is considered to be an 2066 * emergency number if it simply starts with the same digits 2067 * as any of the emergency numbers listed in the RIL / SIM. 2068 * 2069 * @return true if the specified number is an emergency number for a 2070 * local country, based on the CountryDetector. 2071 * 2072 * @see android.location.CountryDetector 2073 * @hide 2074 */ isLocalEmergencyNumberInternal(String number, Context context, boolean useExactMatch)2075 private static boolean isLocalEmergencyNumberInternal(String number, 2076 Context context, 2077 boolean useExactMatch) { 2078 return isLocalEmergencyNumberInternal(getDefaultVoiceSubId(), number, context, 2079 useExactMatch); 2080 } 2081 2082 /** 2083 * Helper function for isLocalEmergencyNumber() and 2084 * isPotentialLocalEmergencyNumber(). 2085 * 2086 * @param subId the subscription id of the SIM. 2087 * @param number the number to look up. 2088 * @param context the specific context which the number should be checked against 2089 * @param useExactMatch if true, consider a number to be an emergency 2090 * number only if it *exactly* matches a number listed in 2091 * the RIL / SIM. If false, a number is considered to be an 2092 * emergency number if it simply starts with the same digits 2093 * as any of the emergency numbers listed in the RIL / SIM. 2094 * 2095 * @return true if the specified number is an emergency number for a 2096 * local country, based on the CountryDetector. 2097 * @hide 2098 */ isLocalEmergencyNumberInternal(int subId, String number, Context context, boolean useExactMatch)2099 private static boolean isLocalEmergencyNumberInternal(int subId, String number, 2100 Context context, 2101 boolean useExactMatch) { 2102 String countryIso; 2103 CountryDetector detector = (CountryDetector) context.getSystemService( 2104 Context.COUNTRY_DETECTOR); 2105 if (detector != null && detector.detectCountry() != null) { 2106 countryIso = detector.detectCountry().getCountryIso(); 2107 } else { 2108 Locale locale = context.getResources().getConfiguration().locale; 2109 countryIso = locale.getCountry(); 2110 Rlog.w(LOG_TAG, "No CountryDetector; falling back to countryIso based on locale: " 2111 + countryIso); 2112 } 2113 return isEmergencyNumberInternal(subId, number, countryIso, useExactMatch); 2114 } 2115 2116 /** 2117 * isVoiceMailNumber: checks a given number against the voicemail 2118 * number provided by the RIL and SIM card. The caller must have 2119 * the READ_PHONE_STATE credential. 2120 * 2121 * @param number the number to look up. 2122 * @return true if the number is in the list of voicemail. False 2123 * otherwise, including if the caller does not have the permission 2124 * to read the VM number. 2125 */ isVoiceMailNumber(String number)2126 public static boolean isVoiceMailNumber(String number) { 2127 return isVoiceMailNumber(SubscriptionManager.getDefaultSubscriptionId(), number); 2128 } 2129 2130 /** 2131 * isVoiceMailNumber: checks a given number against the voicemail 2132 * number provided by the RIL and SIM card. The caller must have 2133 * the READ_PHONE_STATE credential. 2134 * 2135 * @param subId the subscription id of the SIM. 2136 * @param number the number to look up. 2137 * @return true if the number is in the list of voicemail. False 2138 * otherwise, including if the caller does not have the permission 2139 * to read the VM number. 2140 * @hide 2141 */ isVoiceMailNumber(int subId, String number)2142 public static boolean isVoiceMailNumber(int subId, String number) { 2143 return isVoiceMailNumber(null, subId, number); 2144 } 2145 2146 /** 2147 * isVoiceMailNumber: checks a given number against the voicemail 2148 * number provided by the RIL and SIM card. The caller must have 2149 * the READ_PHONE_STATE credential. 2150 * 2151 * @param context {@link Context}. 2152 * @param subId the subscription id of the SIM. 2153 * @param number the number to look up. 2154 * @return true if the number is in the list of voicemail. False 2155 * otherwise, including if the caller does not have the permission 2156 * to read the VM number. 2157 * @hide 2158 */ isVoiceMailNumber(Context context, int subId, String number)2159 public static boolean isVoiceMailNumber(Context context, int subId, String number) { 2160 String vmNumber, mdn; 2161 try { 2162 final TelephonyManager tm; 2163 if (context == null) { 2164 tm = TelephonyManager.getDefault(); 2165 if (DBG) log("isVoiceMailNumber: default tm"); 2166 } else { 2167 tm = TelephonyManager.from(context); 2168 if (DBG) log("isVoiceMailNumber: tm from context"); 2169 } 2170 vmNumber = tm.getVoiceMailNumber(subId); 2171 mdn = tm.getLine1Number(subId); 2172 if (DBG) log("isVoiceMailNumber: mdn=" + mdn + ", vmNumber=" + vmNumber 2173 + ", number=" + number); 2174 } catch (SecurityException ex) { 2175 if (DBG) log("isVoiceMailNumber: SecurityExcpetion caught"); 2176 return false; 2177 } 2178 // Strip the separators from the number before comparing it 2179 // to the list. 2180 number = extractNetworkPortionAlt(number); 2181 if (TextUtils.isEmpty(number)) { 2182 if (DBG) log("isVoiceMailNumber: number is empty after stripping"); 2183 return false; 2184 } 2185 2186 // check if the carrier considers MDN to be an additional voicemail number 2187 boolean compareWithMdn = false; 2188 if (context != null) { 2189 CarrierConfigManager configManager = (CarrierConfigManager) 2190 context.getSystemService(Context.CARRIER_CONFIG_SERVICE); 2191 if (configManager != null) { 2192 PersistableBundle b = configManager.getConfigForSubId(subId); 2193 if (b != null) { 2194 compareWithMdn = b.getBoolean(CarrierConfigManager. 2195 KEY_MDN_IS_ADDITIONAL_VOICEMAIL_NUMBER_BOOL); 2196 if (DBG) log("isVoiceMailNumber: compareWithMdn=" + compareWithMdn); 2197 } 2198 } 2199 } 2200 2201 if (compareWithMdn) { 2202 if (DBG) log("isVoiceMailNumber: treating mdn as additional vm number"); 2203 return compare(number, vmNumber) || compare(number, mdn); 2204 } else { 2205 if (DBG) log("isVoiceMailNumber: returning regular compare"); 2206 return compare(number, vmNumber); 2207 } 2208 } 2209 2210 /** 2211 * Translates any alphabetic letters (i.e. [A-Za-z]) in the 2212 * specified phone number into the equivalent numeric digits, 2213 * according to the phone keypad letter mapping described in 2214 * ITU E.161 and ISO/IEC 9995-8. 2215 * 2216 * @return the input string, with alpha letters converted to numeric 2217 * digits using the phone keypad letter mapping. For example, 2218 * an input of "1-800-GOOG-411" will return "1-800-4664-411". 2219 */ convertKeypadLettersToDigits(String input)2220 public static String convertKeypadLettersToDigits(String input) { 2221 if (input == null) { 2222 return input; 2223 } 2224 int len = input.length(); 2225 if (len == 0) { 2226 return input; 2227 } 2228 2229 char[] out = input.toCharArray(); 2230 2231 for (int i = 0; i < len; i++) { 2232 char c = out[i]; 2233 // If this char isn't in KEYPAD_MAP at all, just leave it alone. 2234 out[i] = (char) KEYPAD_MAP.get(c, c); 2235 } 2236 2237 return new String(out); 2238 } 2239 2240 /** 2241 * The phone keypad letter mapping (see ITU E.161 or ISO/IEC 9995-8.) 2242 * TODO: This should come from a resource. 2243 */ 2244 private static final SparseIntArray KEYPAD_MAP = new SparseIntArray(); 2245 static { 2246 KEYPAD_MAP.put('a', '2'); KEYPAD_MAP.put('b', '2'); KEYPAD_MAP.put('c', '2'); 2247 KEYPAD_MAP.put('A', '2'); KEYPAD_MAP.put('B', '2'); KEYPAD_MAP.put('C', '2'); 2248 2249 KEYPAD_MAP.put('d', '3'); KEYPAD_MAP.put('e', '3'); KEYPAD_MAP.put('f', '3'); 2250 KEYPAD_MAP.put('D', '3'); KEYPAD_MAP.put('E', '3'); KEYPAD_MAP.put('F', '3'); 2251 2252 KEYPAD_MAP.put('g', '4'); KEYPAD_MAP.put('h', '4'); KEYPAD_MAP.put('i', '4'); 2253 KEYPAD_MAP.put('G', '4'); KEYPAD_MAP.put('H', '4'); KEYPAD_MAP.put('I', '4'); 2254 2255 KEYPAD_MAP.put('j', '5'); KEYPAD_MAP.put('k', '5'); KEYPAD_MAP.put('l', '5'); 2256 KEYPAD_MAP.put('J', '5'); KEYPAD_MAP.put('K', '5'); KEYPAD_MAP.put('L', '5'); 2257 2258 KEYPAD_MAP.put('m', '6'); KEYPAD_MAP.put('n', '6'); KEYPAD_MAP.put('o', '6'); 2259 KEYPAD_MAP.put('M', '6'); KEYPAD_MAP.put('N', '6'); KEYPAD_MAP.put('O', '6'); 2260 2261 KEYPAD_MAP.put('p', '7'); KEYPAD_MAP.put('q', '7'); KEYPAD_MAP.put('r', '7'); KEYPAD_MAP.put('s', '7'); 2262 KEYPAD_MAP.put('P', '7'); KEYPAD_MAP.put('Q', '7'); KEYPAD_MAP.put('R', '7'); KEYPAD_MAP.put('S', '7'); 2263 2264 KEYPAD_MAP.put('t', '8'); KEYPAD_MAP.put('u', '8'); KEYPAD_MAP.put('v', '8'); 2265 KEYPAD_MAP.put('T', '8'); KEYPAD_MAP.put('U', '8'); KEYPAD_MAP.put('V', '8'); 2266 2267 KEYPAD_MAP.put('w', '9'); KEYPAD_MAP.put('x', '9'); KEYPAD_MAP.put('y', '9'); KEYPAD_MAP.put('z', '9'); 2268 KEYPAD_MAP.put('W', '9'); KEYPAD_MAP.put('X', '9'); KEYPAD_MAP.put('Y', '9'); KEYPAD_MAP.put('Z', '9'); 2269 } 2270 2271 //================ Plus Code formatting ========================= 2272 private static final char PLUS_SIGN_CHAR = '+'; 2273 private static final String PLUS_SIGN_STRING = "+"; 2274 private static final String NANP_IDP_STRING = "011"; 2275 private static final int NANP_LENGTH = 10; 2276 2277 /** 2278 * This function checks if there is a plus sign (+) in the passed-in dialing number. 2279 * If there is, it processes the plus sign based on the default telephone 2280 * numbering plan of the system when the phone is activated and the current 2281 * telephone numbering plan of the system that the phone is camped on. 2282 * Currently, we only support the case that the default and current telephone 2283 * numbering plans are North American Numbering Plan(NANP). 2284 * 2285 * The passed-in dialStr should only contain the valid format as described below, 2286 * 1) the 1st character in the dialStr should be one of the really dialable 2287 * characters listed below 2288 * ISO-LATIN characters 0-9, *, # , + 2289 * 2) the dialStr should already strip out the separator characters, 2290 * every character in the dialStr should be one of the non separator characters 2291 * listed below 2292 * ISO-LATIN characters 0-9, *, # , +, WILD, WAIT, PAUSE 2293 * 2294 * Otherwise, this function returns the dial string passed in 2295 * 2296 * @param dialStr the original dial string 2297 * @return the converted dial string if the current/default countries belong to NANP, 2298 * and if there is the "+" in the original dial string. Otherwise, the original dial 2299 * string returns. 2300 * 2301 * This API is for CDMA only 2302 * 2303 * @hide TODO: pending API Council approval 2304 */ cdmaCheckAndProcessPlusCode(String dialStr)2305 public static String cdmaCheckAndProcessPlusCode(String dialStr) { 2306 if (!TextUtils.isEmpty(dialStr)) { 2307 if (isReallyDialable(dialStr.charAt(0)) && 2308 isNonSeparator(dialStr)) { 2309 String currIso = TelephonyManager.getDefault().getNetworkCountryIso(); 2310 String defaultIso = TelephonyManager.getDefault().getSimCountryIso(); 2311 if (!TextUtils.isEmpty(currIso) && !TextUtils.isEmpty(defaultIso)) { 2312 return cdmaCheckAndProcessPlusCodeByNumberFormat(dialStr, 2313 getFormatTypeFromCountryCode(currIso), 2314 getFormatTypeFromCountryCode(defaultIso)); 2315 } 2316 } 2317 } 2318 return dialStr; 2319 } 2320 2321 /** 2322 * Process phone number for CDMA, converting plus code using the home network number format. 2323 * This is used for outgoing SMS messages. 2324 * 2325 * @param dialStr the original dial string 2326 * @return the converted dial string 2327 * @hide for internal use 2328 */ cdmaCheckAndProcessPlusCodeForSms(String dialStr)2329 public static String cdmaCheckAndProcessPlusCodeForSms(String dialStr) { 2330 if (!TextUtils.isEmpty(dialStr)) { 2331 if (isReallyDialable(dialStr.charAt(0)) && isNonSeparator(dialStr)) { 2332 String defaultIso = TelephonyManager.getDefault().getSimCountryIso(); 2333 if (!TextUtils.isEmpty(defaultIso)) { 2334 int format = getFormatTypeFromCountryCode(defaultIso); 2335 return cdmaCheckAndProcessPlusCodeByNumberFormat(dialStr, format, format); 2336 } 2337 } 2338 } 2339 return dialStr; 2340 } 2341 2342 /** 2343 * This function should be called from checkAndProcessPlusCode only 2344 * And it is used for test purpose also. 2345 * 2346 * It checks the dial string by looping through the network portion, 2347 * post dial portion 1, post dial porting 2, etc. If there is any 2348 * plus sign, then process the plus sign. 2349 * Currently, this function supports the plus sign conversion within NANP only. 2350 * Specifically, it handles the plus sign in the following ways: 2351 * 1)+1NANP,remove +, e.g. 2352 * +18475797000 is converted to 18475797000, 2353 * 2)+NANP or +non-NANP Numbers,replace + with the current NANP IDP, e.g, 2354 * +8475797000 is converted to 0118475797000, 2355 * +11875767800 is converted to 01111875767800 2356 * 3)+1NANP in post dial string(s), e.g. 2357 * 8475797000;+18475231753 is converted to 8475797000;18475231753 2358 * 2359 * 2360 * @param dialStr the original dial string 2361 * @param currFormat the numbering system of the current country that the phone is camped on 2362 * @param defaultFormat the numbering system of the country that the phone is activated on 2363 * @return the converted dial string if the current/default countries belong to NANP, 2364 * and if there is the "+" in the original dial string. Otherwise, the original dial 2365 * string returns. 2366 * 2367 * @hide 2368 */ 2369 public static String cdmaCheckAndProcessPlusCodeByNumberFormat(String dialStr,int currFormat,int defaultFormat)2370 cdmaCheckAndProcessPlusCodeByNumberFormat(String dialStr,int currFormat,int defaultFormat) { 2371 String retStr = dialStr; 2372 2373 boolean useNanp = (currFormat == defaultFormat) && (currFormat == FORMAT_NANP); 2374 2375 // Checks if the plus sign character is in the passed-in dial string 2376 if (dialStr != null && 2377 dialStr.lastIndexOf(PLUS_SIGN_STRING) != -1) { 2378 2379 // Handle case where default and current telephone numbering plans are NANP. 2380 String postDialStr = null; 2381 String tempDialStr = dialStr; 2382 2383 // Sets the retStr to null since the conversion will be performed below. 2384 retStr = null; 2385 if (DBG) log("checkAndProcessPlusCode,dialStr=" + dialStr); 2386 // This routine is to process the plus sign in the dial string by loop through 2387 // the network portion, post dial portion 1, post dial portion 2... etc. if 2388 // applied 2389 do { 2390 String networkDialStr; 2391 // Format the string based on the rules for the country the number is from, 2392 // and the current country the phone is camped 2393 if (useNanp) { 2394 networkDialStr = extractNetworkPortion(tempDialStr); 2395 } else { 2396 networkDialStr = extractNetworkPortionAlt(tempDialStr); 2397 2398 } 2399 2400 networkDialStr = processPlusCode(networkDialStr, useNanp); 2401 2402 // Concatenates the string that is converted from network portion 2403 if (!TextUtils.isEmpty(networkDialStr)) { 2404 if (retStr == null) { 2405 retStr = networkDialStr; 2406 } else { 2407 retStr = retStr.concat(networkDialStr); 2408 } 2409 } else { 2410 // This should never happen since we checked the if dialStr is null 2411 // and if it contains the plus sign in the beginning of this function. 2412 // The plus sign is part of the network portion. 2413 Rlog.e("checkAndProcessPlusCode: null newDialStr", networkDialStr); 2414 return dialStr; 2415 } 2416 postDialStr = extractPostDialPortion(tempDialStr); 2417 if (!TextUtils.isEmpty(postDialStr)) { 2418 int dialableIndex = findDialableIndexFromPostDialStr(postDialStr); 2419 2420 // dialableIndex should always be greater than 0 2421 if (dialableIndex >= 1) { 2422 retStr = appendPwCharBackToOrigDialStr(dialableIndex, 2423 retStr,postDialStr); 2424 // Skips the P/W character, extracts the dialable portion 2425 tempDialStr = postDialStr.substring(dialableIndex); 2426 } else { 2427 // Non-dialable character such as P/W should not be at the end of 2428 // the dial string after P/W processing in GsmCdmaConnection.java 2429 // Set the postDialStr to "" to break out of the loop 2430 if (dialableIndex < 0) { 2431 postDialStr = ""; 2432 } 2433 Rlog.e("wrong postDialStr=", postDialStr); 2434 } 2435 } 2436 if (DBG) log("checkAndProcessPlusCode,postDialStr=" + postDialStr); 2437 } while (!TextUtils.isEmpty(postDialStr) && !TextUtils.isEmpty(tempDialStr)); 2438 } 2439 return retStr; 2440 } 2441 2442 /** 2443 * Wrap the supplied {@code CharSequence} with a {@code TtsSpan}, annotating it as 2444 * containing a phone number in its entirety. 2445 * 2446 * @param phoneNumber A {@code CharSequence} the entirety of which represents a phone number. 2447 * @return A {@code CharSequence} with appropriate annotations. 2448 */ createTtsSpannable(CharSequence phoneNumber)2449 public static CharSequence createTtsSpannable(CharSequence phoneNumber) { 2450 if (phoneNumber == null) { 2451 return null; 2452 } 2453 Spannable spannable = Spannable.Factory.getInstance().newSpannable(phoneNumber); 2454 PhoneNumberUtils.addTtsSpan(spannable, 0, spannable.length()); 2455 return spannable; 2456 } 2457 2458 /** 2459 * Attach a {@link TtsSpan} to the supplied {@code Spannable} at the indicated location, 2460 * annotating that location as containing a phone number. 2461 * 2462 * @param s A {@code Spannable} to annotate. 2463 * @param start The starting character position of the phone number in {@code s}. 2464 * @param endExclusive The position after the ending character in the phone number {@code s}. 2465 */ addTtsSpan(Spannable s, int start, int endExclusive)2466 public static void addTtsSpan(Spannable s, int start, int endExclusive) { 2467 s.setSpan(createTtsSpan(s.subSequence(start, endExclusive).toString()), 2468 start, 2469 endExclusive, 2470 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 2471 } 2472 2473 /** 2474 * Wrap the supplied {@code CharSequence} with a {@code TtsSpan}, annotating it as 2475 * containing a phone number in its entirety. 2476 * 2477 * @param phoneNumber A {@code CharSequence} the entirety of which represents a phone number. 2478 * @return A {@code CharSequence} with appropriate annotations. 2479 * @deprecated Renamed {@link #createTtsSpannable}. 2480 * 2481 * @hide 2482 */ 2483 @Deprecated ttsSpanAsPhoneNumber(CharSequence phoneNumber)2484 public static CharSequence ttsSpanAsPhoneNumber(CharSequence phoneNumber) { 2485 return createTtsSpannable(phoneNumber); 2486 } 2487 2488 /** 2489 * Attach a {@link TtsSpan} to the supplied {@code Spannable} at the indicated location, 2490 * annotating that location as containing a phone number. 2491 * 2492 * @param s A {@code Spannable} to annotate. 2493 * @param start The starting character position of the phone number in {@code s}. 2494 * @param end The ending character position of the phone number in {@code s}. 2495 * 2496 * @deprecated Renamed {@link #addTtsSpan}. 2497 * 2498 * @hide 2499 */ 2500 @Deprecated ttsSpanAsPhoneNumber(Spannable s, int start, int end)2501 public static void ttsSpanAsPhoneNumber(Spannable s, int start, int end) { 2502 addTtsSpan(s, start, end); 2503 } 2504 2505 /** 2506 * Create a {@code TtsSpan} for the supplied {@code String}. 2507 * 2508 * @param phoneNumberString A {@code String} the entirety of which represents a phone number. 2509 * @return A {@code TtsSpan} for {@param phoneNumberString}. 2510 */ createTtsSpan(String phoneNumberString)2511 public static TtsSpan createTtsSpan(String phoneNumberString) { 2512 if (phoneNumberString == null) { 2513 return null; 2514 } 2515 2516 // Parse the phone number 2517 final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance(); 2518 PhoneNumber phoneNumber = null; 2519 try { 2520 // Don't supply a defaultRegion so this fails for non-international numbers because 2521 // we don't want to TalkBalk to read a country code (e.g. +1) if it is not already 2522 // present 2523 phoneNumber = phoneNumberUtil.parse(phoneNumberString, /* defaultRegion */ null); 2524 } catch (NumberParseException ignored) { 2525 } 2526 2527 // Build a telephone tts span 2528 final TtsSpan.TelephoneBuilder builder = new TtsSpan.TelephoneBuilder(); 2529 if (phoneNumber == null) { 2530 // Strip separators otherwise TalkBack will be silent 2531 // (this behavior was observed with TalkBalk 4.0.2 from their alpha channel) 2532 builder.setNumberParts(splitAtNonNumerics(phoneNumberString)); 2533 } else { 2534 if (phoneNumber.hasCountryCode()) { 2535 builder.setCountryCode(Integer.toString(phoneNumber.getCountryCode())); 2536 } 2537 builder.setNumberParts(Long.toString(phoneNumber.getNationalNumber())); 2538 } 2539 return builder.build(); 2540 } 2541 2542 // Split a phone number like "+20(123)-456#" using spaces, ignoring anything that is not 2543 // a digit or the characters * and #, to produce a result like "20 123 456#". splitAtNonNumerics(CharSequence number)2544 private static String splitAtNonNumerics(CharSequence number) { 2545 StringBuilder sb = new StringBuilder(number.length()); 2546 for (int i = 0; i < number.length(); i++) { 2547 sb.append(PhoneNumberUtils.is12Key(number.charAt(i)) 2548 ? number.charAt(i) 2549 : " "); 2550 } 2551 // It is very important to remove extra spaces. At time of writing, any leading or trailing 2552 // spaces, or any sequence of more than one space, will confuse TalkBack and cause the TTS 2553 // span to be non-functional! 2554 return sb.toString().replaceAll(" +", " ").trim(); 2555 } 2556 getCurrentIdp(boolean useNanp)2557 private static String getCurrentIdp(boolean useNanp) { 2558 String ps = null; 2559 if (useNanp) { 2560 ps = NANP_IDP_STRING; 2561 } else { 2562 // in case, there is no IDD is found, we shouldn't convert it. 2563 ps = SystemProperties.get(PROPERTY_OPERATOR_IDP_STRING, PLUS_SIGN_STRING); 2564 } 2565 return ps; 2566 } 2567 isTwoToNine(char c)2568 private static boolean isTwoToNine (char c) { 2569 if (c >= '2' && c <= '9') { 2570 return true; 2571 } else { 2572 return false; 2573 } 2574 } 2575 getFormatTypeFromCountryCode(String country)2576 private static int getFormatTypeFromCountryCode (String country) { 2577 // Check for the NANP countries 2578 int length = NANP_COUNTRIES.length; 2579 for (int i = 0; i < length; i++) { 2580 if (NANP_COUNTRIES[i].compareToIgnoreCase(country) == 0) { 2581 return FORMAT_NANP; 2582 } 2583 } 2584 if ("jp".compareToIgnoreCase(country) == 0) { 2585 return FORMAT_JAPAN; 2586 } 2587 return FORMAT_UNKNOWN; 2588 } 2589 2590 /** 2591 * This function checks if the passed in string conforms to the NANP format 2592 * i.e. NXX-NXX-XXXX, N is any digit 2-9 and X is any digit 0-9 2593 * @hide 2594 */ isNanp(String dialStr)2595 public static boolean isNanp (String dialStr) { 2596 boolean retVal = false; 2597 if (dialStr != null) { 2598 if (dialStr.length() == NANP_LENGTH) { 2599 if (isTwoToNine(dialStr.charAt(0)) && 2600 isTwoToNine(dialStr.charAt(3))) { 2601 retVal = true; 2602 for (int i=1; i<NANP_LENGTH; i++ ) { 2603 char c=dialStr.charAt(i); 2604 if (!PhoneNumberUtils.isISODigit(c)) { 2605 retVal = false; 2606 break; 2607 } 2608 } 2609 } 2610 } 2611 } else { 2612 Rlog.e("isNanp: null dialStr passed in", dialStr); 2613 } 2614 return retVal; 2615 } 2616 2617 /** 2618 * This function checks if the passed in string conforms to 1-NANP format 2619 */ isOneNanp(String dialStr)2620 private static boolean isOneNanp(String dialStr) { 2621 boolean retVal = false; 2622 if (dialStr != null) { 2623 String newDialStr = dialStr.substring(1); 2624 if ((dialStr.charAt(0) == '1') && isNanp(newDialStr)) { 2625 retVal = true; 2626 } 2627 } else { 2628 Rlog.e("isOneNanp: null dialStr passed in", dialStr); 2629 } 2630 return retVal; 2631 } 2632 2633 /** 2634 * Determines if the specified number is actually a URI 2635 * (i.e. a SIP address) rather than a regular PSTN phone number, 2636 * based on whether or not the number contains an "@" character. 2637 * 2638 * @hide 2639 * @param number 2640 * @return true if number contains @ 2641 */ isUriNumber(String number)2642 public static boolean isUriNumber(String number) { 2643 // Note we allow either "@" or "%40" to indicate a URI, in case 2644 // the passed-in string is URI-escaped. (Neither "@" nor "%40" 2645 // will ever be found in a legal PSTN number.) 2646 return number != null && (number.contains("@") || number.contains("%40")); 2647 } 2648 2649 /** 2650 * @return the "username" part of the specified SIP address, 2651 * i.e. the part before the "@" character (or "%40"). 2652 * 2653 * @param number SIP address of the form "username@domainname" 2654 * (or the URI-escaped equivalent "username%40domainname") 2655 * @see #isUriNumber 2656 * 2657 * @hide 2658 */ getUsernameFromUriNumber(String number)2659 public static String getUsernameFromUriNumber(String number) { 2660 // The delimiter between username and domain name can be 2661 // either "@" or "%40" (the URI-escaped equivalent.) 2662 int delimiterIndex = number.indexOf('@'); 2663 if (delimiterIndex < 0) { 2664 delimiterIndex = number.indexOf("%40"); 2665 } 2666 if (delimiterIndex < 0) { 2667 Rlog.w(LOG_TAG, 2668 "getUsernameFromUriNumber: no delimiter found in SIP addr '" + number + "'"); 2669 delimiterIndex = number.length(); 2670 } 2671 return number.substring(0, delimiterIndex); 2672 } 2673 2674 /** 2675 * Given a {@link Uri} with a {@code sip} scheme, attempts to build an equivalent {@code tel} 2676 * scheme {@link Uri}. If the source {@link Uri} does not contain a valid number, or is not 2677 * using the {@code sip} scheme, the original {@link Uri} is returned. 2678 * 2679 * @param source The {@link Uri} to convert. 2680 * @return The equivalent {@code tel} scheme {@link Uri}. 2681 * 2682 * @hide 2683 */ convertSipUriToTelUri(Uri source)2684 public static Uri convertSipUriToTelUri(Uri source) { 2685 // A valid SIP uri has the format: sip:user:password@host:port;uri-parameters?headers 2686 // Per RFC3261, the "user" can be a telephone number. 2687 // For example: sip:1650555121;phone-context=blah.com@host.com 2688 // In this case, the phone number is in the user field of the URI, and the parameters can be 2689 // ignored. 2690 // 2691 // A SIP URI can also specify a phone number in a format similar to: 2692 // sip:+1-212-555-1212@something.com;user=phone 2693 // In this case, the phone number is again in user field and the parameters can be ignored. 2694 // We can get the user field in these instances by splitting the string on the @, ;, or : 2695 // and looking at the first found item. 2696 2697 String scheme = source.getScheme(); 2698 2699 if (!PhoneAccount.SCHEME_SIP.equals(scheme)) { 2700 // Not a sip URI, bail. 2701 return source; 2702 } 2703 2704 String number = source.getSchemeSpecificPart(); 2705 String numberParts[] = number.split("[@;:]"); 2706 2707 if (numberParts.length == 0) { 2708 // Number not found, bail. 2709 return source; 2710 } 2711 number = numberParts[0]; 2712 2713 return Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null); 2714 } 2715 2716 /** 2717 * This function handles the plus code conversion 2718 * If the number format is 2719 * 1)+1NANP,remove +, 2720 * 2)other than +1NANP, any + numbers,replace + with the current IDP 2721 */ processPlusCode(String networkDialStr, boolean useNanp)2722 private static String processPlusCode(String networkDialStr, boolean useNanp) { 2723 String retStr = networkDialStr; 2724 2725 if (DBG) log("processPlusCode, networkDialStr = " + networkDialStr 2726 + "for NANP = " + useNanp); 2727 // If there is a plus sign at the beginning of the dial string, 2728 // Convert the plus sign to the default IDP since it's an international number 2729 if (networkDialStr != null && 2730 networkDialStr.charAt(0) == PLUS_SIGN_CHAR && 2731 networkDialStr.length() > 1) { 2732 String newStr = networkDialStr.substring(1); 2733 // TODO: for nonNanp, should the '+' be removed if following number is country code 2734 if (useNanp && isOneNanp(newStr)) { 2735 // Remove the leading plus sign 2736 retStr = newStr; 2737 } else { 2738 // Replaces the plus sign with the default IDP 2739 retStr = networkDialStr.replaceFirst("[+]", getCurrentIdp(useNanp)); 2740 } 2741 } 2742 if (DBG) log("processPlusCode, retStr=" + retStr); 2743 return retStr; 2744 } 2745 2746 // This function finds the index of the dialable character(s) 2747 // in the post dial string findDialableIndexFromPostDialStr(String postDialStr)2748 private static int findDialableIndexFromPostDialStr(String postDialStr) { 2749 for (int index = 0;index < postDialStr.length();index++) { 2750 char c = postDialStr.charAt(index); 2751 if (isReallyDialable(c)) { 2752 return index; 2753 } 2754 } 2755 return -1; 2756 } 2757 2758 // This function appends the non-dialable P/W character to the original 2759 // dial string based on the dialable index passed in 2760 private static String appendPwCharBackToOrigDialStr(int dialableIndex,String origStr, String dialStr)2761 appendPwCharBackToOrigDialStr(int dialableIndex,String origStr, String dialStr) { 2762 String retStr; 2763 2764 // There is only 1 P/W character before the dialable characters 2765 if (dialableIndex == 1) { 2766 StringBuilder ret = new StringBuilder(origStr); 2767 ret = ret.append(dialStr.charAt(0)); 2768 retStr = ret.toString(); 2769 } else { 2770 // It means more than 1 P/W characters in the post dial string, 2771 // appends to retStr 2772 String nonDigitStr = dialStr.substring(0,dialableIndex); 2773 retStr = origStr.concat(nonDigitStr); 2774 } 2775 return retStr; 2776 } 2777 2778 //===== Beginning of utility methods used in compareLoosely() ===== 2779 2780 /** 2781 * Phone numbers are stored in "lookup" form in the database 2782 * as reversed strings to allow for caller ID lookup 2783 * 2784 * This method takes a phone number and makes a valid SQL "LIKE" 2785 * string that will match the lookup form 2786 * 2787 */ 2788 /** all of a up to len must be an international prefix or 2789 * separators/non-dialing digits 2790 */ 2791 private static boolean matchIntlPrefix(String a, int len)2792 matchIntlPrefix(String a, int len) { 2793 /* '([^0-9*#+pwn]\+[^0-9*#+pwn] | [^0-9*#+pwn]0(0|11)[^0-9*#+pwn] )$' */ 2794 /* 0 1 2 3 45 */ 2795 2796 int state = 0; 2797 for (int i = 0 ; i < len ; i++) { 2798 char c = a.charAt(i); 2799 2800 switch (state) { 2801 case 0: 2802 if (c == '+') state = 1; 2803 else if (c == '0') state = 2; 2804 else if (isNonSeparator(c)) return false; 2805 break; 2806 2807 case 2: 2808 if (c == '0') state = 3; 2809 else if (c == '1') state = 4; 2810 else if (isNonSeparator(c)) return false; 2811 break; 2812 2813 case 4: 2814 if (c == '1') state = 5; 2815 else if (isNonSeparator(c)) return false; 2816 break; 2817 2818 default: 2819 if (isNonSeparator(c)) return false; 2820 break; 2821 2822 } 2823 } 2824 2825 return state == 1 || state == 3 || state == 5; 2826 } 2827 2828 /** all of 'a' up to len must be a (+|00|011)country code) 2829 * We're fast and loose with the country code. Any \d{1,3} matches */ 2830 private static boolean matchIntlPrefixAndCC(String a, int len)2831 matchIntlPrefixAndCC(String a, int len) { 2832 /* [^0-9*#+pwn]*(\+|0(0|11)\d\d?\d? [^0-9*#+pwn] $ */ 2833 /* 0 1 2 3 45 6 7 8 */ 2834 2835 int state = 0; 2836 for (int i = 0 ; i < len ; i++ ) { 2837 char c = a.charAt(i); 2838 2839 switch (state) { 2840 case 0: 2841 if (c == '+') state = 1; 2842 else if (c == '0') state = 2; 2843 else if (isNonSeparator(c)) return false; 2844 break; 2845 2846 case 2: 2847 if (c == '0') state = 3; 2848 else if (c == '1') state = 4; 2849 else if (isNonSeparator(c)) return false; 2850 break; 2851 2852 case 4: 2853 if (c == '1') state = 5; 2854 else if (isNonSeparator(c)) return false; 2855 break; 2856 2857 case 1: 2858 case 3: 2859 case 5: 2860 if (isISODigit(c)) state = 6; 2861 else if (isNonSeparator(c)) return false; 2862 break; 2863 2864 case 6: 2865 case 7: 2866 if (isISODigit(c)) state++; 2867 else if (isNonSeparator(c)) return false; 2868 break; 2869 2870 default: 2871 if (isNonSeparator(c)) return false; 2872 } 2873 } 2874 2875 return state == 6 || state == 7 || state == 8; 2876 } 2877 2878 /** all of 'a' up to len must match non-US trunk prefix ('0') */ 2879 private static boolean matchTrunkPrefix(String a, int len)2880 matchTrunkPrefix(String a, int len) { 2881 boolean found; 2882 2883 found = false; 2884 2885 for (int i = 0 ; i < len ; i++) { 2886 char c = a.charAt(i); 2887 2888 if (c == '0' && !found) { 2889 found = true; 2890 } else if (isNonSeparator(c)) { 2891 return false; 2892 } 2893 } 2894 2895 return found; 2896 } 2897 2898 //===== End of utility methods used only in compareLoosely() ===== 2899 2900 //===== Beginning of utility methods used only in compareStrictly() ==== 2901 2902 /* 2903 * If true, the number is country calling code. 2904 */ 2905 private static final boolean COUNTRY_CALLING_CALL[] = { 2906 true, true, false, false, false, false, false, true, false, false, 2907 false, false, false, false, false, false, false, false, false, false, 2908 true, false, false, false, false, false, false, true, true, false, 2909 true, true, true, true, true, false, true, false, false, true, 2910 true, false, false, true, true, true, true, true, true, true, 2911 false, true, true, true, true, true, true, true, true, false, 2912 true, true, true, true, true, true, true, false, false, false, 2913 false, false, false, false, false, false, false, false, false, false, 2914 false, true, true, true, true, false, true, false, false, true, 2915 true, true, true, true, true, true, false, false, true, false, 2916 }; 2917 private static final int CCC_LENGTH = COUNTRY_CALLING_CALL.length; 2918 2919 /** 2920 * @return true when input is valid Country Calling Code. 2921 */ isCountryCallingCode(int countryCallingCodeCandidate)2922 private static boolean isCountryCallingCode(int countryCallingCodeCandidate) { 2923 return countryCallingCodeCandidate > 0 && countryCallingCodeCandidate < CCC_LENGTH && 2924 COUNTRY_CALLING_CALL[countryCallingCodeCandidate]; 2925 } 2926 2927 /** 2928 * Returns integer corresponding to the input if input "ch" is 2929 * ISO-LATIN characters 0-9. 2930 * Returns -1 otherwise 2931 */ tryGetISODigit(char ch)2932 private static int tryGetISODigit(char ch) { 2933 if ('0' <= ch && ch <= '9') { 2934 return ch - '0'; 2935 } else { 2936 return -1; 2937 } 2938 } 2939 2940 private static class CountryCallingCodeAndNewIndex { 2941 public final int countryCallingCode; 2942 public final int newIndex; CountryCallingCodeAndNewIndex(int countryCode, int newIndex)2943 public CountryCallingCodeAndNewIndex(int countryCode, int newIndex) { 2944 this.countryCallingCode = countryCode; 2945 this.newIndex = newIndex; 2946 } 2947 } 2948 2949 /* 2950 * Note that this function does not strictly care the country calling code with 2951 * 3 length (like Morocco: +212), assuming it is enough to use the first two 2952 * digit to compare two phone numbers. 2953 */ tryGetCountryCallingCodeAndNewIndex( String str, boolean acceptThailandCase)2954 private static CountryCallingCodeAndNewIndex tryGetCountryCallingCodeAndNewIndex( 2955 String str, boolean acceptThailandCase) { 2956 // Rough regexp: 2957 // ^[^0-9*#+]*((\+|0(0|11)\d\d?|166) [^0-9*#+] $ 2958 // 0 1 2 3 45 6 7 89 2959 // 2960 // In all the states, this function ignores separator characters. 2961 // "166" is the special case for the call from Thailand to the US. Uguu! 2962 int state = 0; 2963 int ccc = 0; 2964 final int length = str.length(); 2965 for (int i = 0 ; i < length ; i++ ) { 2966 char ch = str.charAt(i); 2967 switch (state) { 2968 case 0: 2969 if (ch == '+') state = 1; 2970 else if (ch == '0') state = 2; 2971 else if (ch == '1') { 2972 if (acceptThailandCase) { 2973 state = 8; 2974 } else { 2975 return null; 2976 } 2977 } else if (isDialable(ch)) { 2978 return null; 2979 } 2980 break; 2981 2982 case 2: 2983 if (ch == '0') state = 3; 2984 else if (ch == '1') state = 4; 2985 else if (isDialable(ch)) { 2986 return null; 2987 } 2988 break; 2989 2990 case 4: 2991 if (ch == '1') state = 5; 2992 else if (isDialable(ch)) { 2993 return null; 2994 } 2995 break; 2996 2997 case 1: 2998 case 3: 2999 case 5: 3000 case 6: 3001 case 7: 3002 { 3003 int ret = tryGetISODigit(ch); 3004 if (ret > 0) { 3005 ccc = ccc * 10 + ret; 3006 if (ccc >= 100 || isCountryCallingCode(ccc)) { 3007 return new CountryCallingCodeAndNewIndex(ccc, i + 1); 3008 } 3009 if (state == 1 || state == 3 || state == 5) { 3010 state = 6; 3011 } else { 3012 state++; 3013 } 3014 } else if (isDialable(ch)) { 3015 return null; 3016 } 3017 } 3018 break; 3019 case 8: 3020 if (ch == '6') state = 9; 3021 else if (isDialable(ch)) { 3022 return null; 3023 } 3024 break; 3025 case 9: 3026 if (ch == '6') { 3027 return new CountryCallingCodeAndNewIndex(66, i + 1); 3028 } else { 3029 return null; 3030 } 3031 default: 3032 return null; 3033 } 3034 } 3035 3036 return null; 3037 } 3038 3039 /** 3040 * Currently this function simply ignore the first digit assuming it is 3041 * trunk prefix. Actually trunk prefix is different in each country. 3042 * 3043 * e.g. 3044 * "+79161234567" equals "89161234567" (Russian trunk digit is 8) 3045 * "+33123456789" equals "0123456789" (French trunk digit is 0) 3046 * 3047 */ tryGetTrunkPrefixOmittedIndex(String str, int currentIndex)3048 private static int tryGetTrunkPrefixOmittedIndex(String str, int currentIndex) { 3049 int length = str.length(); 3050 for (int i = currentIndex ; i < length ; i++) { 3051 final char ch = str.charAt(i); 3052 if (tryGetISODigit(ch) >= 0) { 3053 return i + 1; 3054 } else if (isDialable(ch)) { 3055 return -1; 3056 } 3057 } 3058 return -1; 3059 } 3060 3061 /** 3062 * Return true if the prefix of "str" is "ignorable". Here, "ignorable" means 3063 * that "str" has only one digit and separator characters. The one digit is 3064 * assumed to be trunk prefix. 3065 */ checkPrefixIsIgnorable(final String str, int forwardIndex, int backwardIndex)3066 private static boolean checkPrefixIsIgnorable(final String str, 3067 int forwardIndex, int backwardIndex) { 3068 boolean trunk_prefix_was_read = false; 3069 while (backwardIndex >= forwardIndex) { 3070 if (tryGetISODigit(str.charAt(backwardIndex)) >= 0) { 3071 if (trunk_prefix_was_read) { 3072 // More than one digit appeared, meaning that "a" and "b" 3073 // is different. 3074 return false; 3075 } else { 3076 // Ignore just one digit, assuming it is trunk prefix. 3077 trunk_prefix_was_read = true; 3078 } 3079 } else if (isDialable(str.charAt(backwardIndex))) { 3080 // Trunk prefix is a digit, not "*", "#"... 3081 return false; 3082 } 3083 backwardIndex--; 3084 } 3085 3086 return true; 3087 } 3088 3089 /** 3090 * Returns Default voice subscription Id. 3091 */ getDefaultVoiceSubId()3092 private static int getDefaultVoiceSubId() { 3093 return SubscriptionManager.getDefaultVoiceSubscriptionId(); 3094 } 3095 //==== End of utility methods used only in compareStrictly() ===== 3096 3097 3098 /* 3099 * The config held calling number conversion map, expected to convert to emergency number. 3100 */ 3101 private static String[] sConvertToEmergencyMap = null; 3102 3103 /** 3104 * Converts to emergency number based on the conversion map. 3105 * The conversion map is declared as config_convert_to_emergency_number_map. 3106 * 3107 * @param context a context to use for accessing resources 3108 * @return The converted emergency number if the number matches conversion map, 3109 * otherwise original number. 3110 * 3111 * @hide 3112 */ convertToEmergencyNumber(Context context, String number)3113 public static String convertToEmergencyNumber(Context context, String number) { 3114 if (context == null || TextUtils.isEmpty(number)) { 3115 return number; 3116 } 3117 3118 String normalizedNumber = normalizeNumber(number); 3119 3120 // The number is already emergency number. Skip conversion. 3121 if (isEmergencyNumber(normalizedNumber)) { 3122 return number; 3123 } 3124 3125 if (sConvertToEmergencyMap == null) { 3126 sConvertToEmergencyMap = context.getResources().getStringArray( 3127 com.android.internal.R.array.config_convert_to_emergency_number_map); 3128 } 3129 3130 // The conversion map is not defined (this is default). Skip conversion. 3131 if (sConvertToEmergencyMap == null || sConvertToEmergencyMap.length == 0 ) { 3132 return number; 3133 } 3134 3135 for (String convertMap : sConvertToEmergencyMap) { 3136 if (DBG) log("convertToEmergencyNumber: " + convertMap); 3137 String[] entry = null; 3138 String[] filterNumbers = null; 3139 String convertedNumber = null; 3140 if (!TextUtils.isEmpty(convertMap)) { 3141 entry = convertMap.split(":"); 3142 } 3143 if (entry != null && entry.length == 2) { 3144 convertedNumber = entry[1]; 3145 if (!TextUtils.isEmpty(entry[0])) { 3146 filterNumbers = entry[0].split(","); 3147 } 3148 } 3149 // Skip if the format of entry is invalid 3150 if (TextUtils.isEmpty(convertedNumber) || filterNumbers == null 3151 || filterNumbers.length == 0) { 3152 continue; 3153 } 3154 3155 for (String filterNumber : filterNumbers) { 3156 if (DBG) log("convertToEmergencyNumber: filterNumber = " + filterNumber 3157 + ", convertedNumber = " + convertedNumber); 3158 if (!TextUtils.isEmpty(filterNumber) && filterNumber.equals(normalizedNumber)) { 3159 if (DBG) log("convertToEmergencyNumber: Matched. Successfully converted to: " 3160 + convertedNumber); 3161 return convertedNumber; 3162 } 3163 } 3164 } 3165 return number; 3166 } 3167 } 3168