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