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