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