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