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