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