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