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