/*
 * Copyright (C) 2006 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.telephony;

import com.android.i18n.phonenumbers.NumberParseException;
import com.android.i18n.phonenumbers.PhoneNumberUtil;
import com.android.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
import com.android.i18n.phonenumbers.Phonenumber.PhoneNumber;
import com.android.i18n.phonenumbers.ShortNumberInfo;

import android.annotation.IntDef;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.database.Cursor;
import android.location.CountryDetector;
import android.net.Uri;
import android.os.SystemProperties;
import android.os.PersistableBundle;
import android.provider.Contacts;
import android.provider.ContactsContract;
import android.telecom.PhoneAccount;
import android.text.Editable;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.TtsSpan;
import android.util.SparseIntArray;

import static com.android.internal.telephony.TelephonyProperties.PROPERTY_OPERATOR_IDP_STRING;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Various utilities for dealing with phone number strings.
/** * Various utilities for dealing with phone number strings. */
public class PhoneNumberUtils {
{@hide}
/** {@hide} */
@IntDef(prefix = "BCD_EXTENDED_TYPE_", value = { BCD_EXTENDED_TYPE_EF_ADN, BCD_EXTENDED_TYPE_CALLED_PARTY, }) @Retention(RetentionPolicy.SOURCE) public @interface BcdExtendType {} /* * The BCD extended type used to determine the extended char for the digit which is greater than * 9. * * see TS 51.011 section 10.5.1 EF_ADN(Abbreviated dialling numbers) */ public static final int BCD_EXTENDED_TYPE_EF_ADN = 1; /* * The BCD extended type used to determine the extended char for the digit which is greater than * 9. * * see TS 24.008 section 10.5.4.7 Called party BCD number */ public static final int BCD_EXTENDED_TYPE_CALLED_PARTY = 2; /* * Special characters * * (See "What is a phone number?" doc) * 'p' --- GSM pause character, same as comma * 'n' --- GSM wild character * 'w' --- GSM wait character */ public static final char PAUSE = ','; public static final char WAIT = ';'; public static final char WILD = 'N'; /* * Calling Line Identification Restriction (CLIR) */ private static final String CLIR_ON = "*31#"; private static final String CLIR_OFF = "#31#"; /* * TOA = TON + NPI * See TS 24.008 section 10.5.4.7 for details. * These are the only really useful TOA values */ public static final int TOA_International = 0x91; public static final int TOA_Unknown = 0x81; static final String LOG_TAG = "PhoneNumberUtils"; private static final boolean DBG = false; private static final String BCD_EF_ADN_EXTENDED = "*#,N;"; private static final String BCD_CALLED_PARTY_EXTENDED = "*#abc"; /* * global-phone-number = ["+"] 1*( DIGIT / written-sep ) * written-sep = ("-"/".") */ private static final Pattern GLOBAL_PHONE_NUMBER_PATTERN = Pattern.compile("[\\+]?[0-9.-]+");
True if c is ISO-LATIN characters 0-9
/** True if c is ISO-LATIN characters 0-9 */
public static boolean isISODigit (char c) { return c >= '0' && c <= '9'; }
True if c is ISO-LATIN characters 0-9, *, #
/** True if c is ISO-LATIN characters 0-9, *, # */
public final static boolean is12Key(char c) { return (c >= '0' && c <= '9') || c == '*' || c == '#'; }
True if c is ISO-LATIN characters 0-9, *, # , +, WILD
/** True if c is ISO-LATIN characters 0-9, *, # , +, WILD */
public final static boolean isDialable(char c) { return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+' || c == WILD; }
True if c is ISO-LATIN characters 0-9, *, # , + (no WILD)
/** True if c is ISO-LATIN characters 0-9, *, # , + (no WILD) */
public final static boolean isReallyDialable(char c) { return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+'; }
True if c is ISO-LATIN characters 0-9, *, # , +, WILD, WAIT, PAUSE
/** True if c is ISO-LATIN characters 0-9, *, # , +, WILD, WAIT, PAUSE */
public final static boolean isNonSeparator(char c) { return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+' || c == WILD || c == WAIT || c == PAUSE; }
This any anything to the right of this char is part of the post-dial string (eg this is PAUSE or WAIT)
/** This any anything to the right of this char is part of the * post-dial string (eg this is PAUSE or WAIT) */
public final static boolean isStartsPostDial (char c) { return c == PAUSE || c == WAIT; } private static boolean isPause (char c){ return c == 'p'||c == 'P'; } private static boolean isToneWait (char c){ return c == 'w'||c == 'W'; }
Returns true if ch is not dialable or alpha char
/** Returns true if ch is not dialable or alpha char */
private static boolean isSeparator(char ch) { return !isDialable(ch) && !(('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z')); }
Extracts the phone number from an Intent.
Params:
  • intent – the intent to get the number of
  • context – a context to use for database access
Returns:the phone number that would be called by the intent, or null if the number cannot be found.
/** Extracts the phone number from an Intent. * * @param intent the intent to get the number of * @param context a context to use for database access * * @return the phone number that would be called by the intent, or * <code>null</code> if the number cannot be found. */
public static String getNumberFromIntent(Intent intent, Context context) { String number = null; Uri uri = intent.getData(); if (uri == null) { return null; } String scheme = uri.getScheme(); if (scheme.equals("tel") || scheme.equals("sip")) { return uri.getSchemeSpecificPart(); } if (context == null) { return null; } String type = intent.resolveType(context); String phoneColumn = null; // Correctly read out the phone entry based on requested provider final String authority = uri.getAuthority(); if (Contacts.AUTHORITY.equals(authority)) { phoneColumn = Contacts.People.Phones.NUMBER; } else if (ContactsContract.AUTHORITY.equals(authority)) { phoneColumn = ContactsContract.CommonDataKinds.Phone.NUMBER; } Cursor c = null; try { c = context.getContentResolver().query(uri, new String[] { phoneColumn }, null, null, null); if (c != null) { if (c.moveToFirst()) { number = c.getString(c.getColumnIndex(phoneColumn)); } } } catch (RuntimeException e) { Rlog.e(LOG_TAG, "Error getting phone number.", e); } finally { if (c != null) { c.close(); } } return number; }
Extracts the network address portion and canonicalizes (filters out separators.) Network address portion is everything up to DTMF control digit separators (pause or wait), but without non-dialable characters. Please note that the GSM wild character is allowed in the result. This must be resolved before dialing. Returns null if phoneNumber == null
/** Extracts the network address portion and canonicalizes * (filters out separators.) * Network address portion is everything up to DTMF control digit * separators (pause or wait), but without non-dialable characters. * * Please note that the GSM wild character is allowed in the result. * This must be resolved before dialing. * * Returns null if phoneNumber == null */
public static String extractNetworkPortion(String phoneNumber) { if (phoneNumber == null) { return null; } int len = phoneNumber.length(); StringBuilder ret = new StringBuilder(len); for (int i = 0; i < len; i++) { char c = phoneNumber.charAt(i); // Character.digit() supports ASCII and Unicode digits (fullwidth, Arabic-Indic, etc.) int digit = Character.digit(c, 10); if (digit != -1) { ret.append(digit); } else if (c == '+') { // Allow '+' as first character or after CLIR MMI prefix String prefix = ret.toString(); if (prefix.length() == 0 || prefix.equals(CLIR_ON) || prefix.equals(CLIR_OFF)) { ret.append(c); } } else if (isDialable(c)) { ret.append(c); } else if (isStartsPostDial (c)) { break; } } return ret.toString(); }
Extracts the network address portion and canonicalize. This function is equivalent to extractNetworkPortion(), except for allowing the PLUS character to occur at arbitrary positions in the address portion, not just the first position.
@hide
/** * Extracts the network address portion and canonicalize. * * This function is equivalent to extractNetworkPortion(), except * for allowing the PLUS character to occur at arbitrary positions * in the address portion, not just the first position. * * @hide */
public static String extractNetworkPortionAlt(String phoneNumber) { if (phoneNumber == null) { return null; } int len = phoneNumber.length(); StringBuilder ret = new StringBuilder(len); boolean haveSeenPlus = false; for (int i = 0; i < len; i++) { char c = phoneNumber.charAt(i); if (c == '+') { if (haveSeenPlus) { continue; } haveSeenPlus = true; } if (isDialable(c)) { ret.append(c); } else if (isStartsPostDial (c)) { break; } } return ret.toString(); }
Strips separators from a phone number string.
Params:
  • phoneNumber – phone number to strip.
Returns:phone string stripped of separators.
/** * Strips separators from a phone number string. * @param phoneNumber phone number to strip. * @return phone string stripped of separators. */
public static String stripSeparators(String phoneNumber) { if (phoneNumber == null) { return null; } int len = phoneNumber.length(); StringBuilder ret = new StringBuilder(len); for (int i = 0; i < len; i++) { char c = phoneNumber.charAt(i); // Character.digit() supports ASCII and Unicode digits (fullwidth, Arabic-Indic, etc.) int digit = Character.digit(c, 10); if (digit != -1) { ret.append(digit); } else if (isNonSeparator(c)) { ret.append(c); } } return ret.toString(); }
Translates keypad letters to actual digits (e.g. 1-800-GOOG-411 will become 1-800-4664-411), and then strips all separators (e.g. 1-800-4664-411 will become 18004664411).
See Also:
@hide
/** * Translates keypad letters to actual digits (e.g. 1-800-GOOG-411 will * become 1-800-4664-411), and then strips all separators (e.g. 1-800-4664-411 will become * 18004664411). * * @see #convertKeypadLettersToDigits(String) * @see #stripSeparators(String) * * @hide */
public static String convertAndStrip(String phoneNumber) { return stripSeparators(convertKeypadLettersToDigits(phoneNumber)); }
Converts pause and tonewait pause characters to Android representation. RFC 3601 says pause is 'p' and tonewait is 'w'.
@hide
/** * Converts pause and tonewait pause characters * to Android representation. * RFC 3601 says pause is 'p' and tonewait is 'w'. * @hide */
public static String convertPreDial(String phoneNumber) { if (phoneNumber == null) { return null; } int len = phoneNumber.length(); StringBuilder ret = new StringBuilder(len); for (int i = 0; i < len; i++) { char c = phoneNumber.charAt(i); if (isPause(c)) { c = PAUSE; } else if (isToneWait(c)) { c = WAIT; } ret.append(c); } return ret.toString(); }
or -1 if both are negative
/** or -1 if both are negative */
static private int minPositive (int a, int b) { if (a >= 0 && b >= 0) { return (a < b) ? a : b; } else if (a >= 0) { /* && b < 0 */ return a; } else if (b >= 0) { /* && a < 0 */ return b; } else { /* a < 0 && b < 0 */ return -1; } } private static void log(String msg) { Rlog.d(LOG_TAG, msg); }
index of the last character of the network portion (eg anything after is a post-dial string)
/** index of the last character of the network portion * (eg anything after is a post-dial string) */
static private int indexOfLastNetworkChar(String a) { int pIndex, wIndex; int origLength; int trimIndex; origLength = a.length(); pIndex = a.indexOf(PAUSE); wIndex = a.indexOf(WAIT); trimIndex = minPositive(pIndex, wIndex); if (trimIndex < 0) { return origLength - 1; } else { return trimIndex - 1; } }
Extracts the post-dial sequence of DTMF control digits, pauses, and waits. Strips separators. This string may be empty, but will not be null unless phoneNumber == null. Returns null if phoneNumber == null
/** * Extracts the post-dial sequence of DTMF control digits, pauses, and * waits. Strips separators. This string may be empty, but will not be null * unless phoneNumber == null. * * Returns null if phoneNumber == null */
public static String extractPostDialPortion(String phoneNumber) { if (phoneNumber == null) return null; int trimIndex; StringBuilder ret = new StringBuilder(); trimIndex = indexOfLastNetworkChar (phoneNumber); for (int i = trimIndex + 1, s = phoneNumber.length() ; i < s; i++ ) { char c = phoneNumber.charAt(i); if (isNonSeparator(c)) { ret.append(c); } } return ret.toString(); }
Compare phone numbers a and b, return true if they're identical enough for caller ID purposes.
/** * Compare phone numbers a and b, return true if they're identical enough for caller ID purposes. */
public static boolean compare(String a, String b) { // We've used loose comparation at least Eclair, which may change in the future. return compare(a, b, false); }
Compare phone numbers a and b, and return true if they're identical enough for caller ID purposes. Checks a resource to determine whether to use a strict or loose comparison algorithm.
/** * Compare phone numbers a and b, and return true if they're identical * enough for caller ID purposes. Checks a resource to determine whether * to use a strict or loose comparison algorithm. */
public static boolean compare(Context context, String a, String b) { boolean useStrict = context.getResources().getBoolean( com.android.internal.R.bool.config_use_strict_phone_number_comparation); return compare(a, b, useStrict); }
@hideonly for testing.
/** * @hide only for testing. */
public static boolean compare(String a, String b, boolean useStrictComparation) { return (useStrictComparation ? compareStrictly(a, b) : compareLoosely(a, b)); }
Compare phone numbers a and b, return true if they're identical enough for caller ID purposes. - Compares from right to left - requires MIN_MATCH (7) characters to match - handles common trunk prefixes and international prefixes (basically, everything except the Russian trunk prefix) Note that this method does not return false even when the two phone numbers are not exactly same; rather; we can call this method "similar()", not "equals()".
@hide
/** * Compare phone numbers a and b, return true if they're identical * enough for caller ID purposes. * * - Compares from right to left * - requires MIN_MATCH (7) characters to match * - handles common trunk prefixes and international prefixes * (basically, everything except the Russian trunk prefix) * * Note that this method does not return false even when the two phone numbers * are not exactly same; rather; we can call this method "similar()", not "equals()". * * @hide */
public static boolean compareLoosely(String a, String b) { int ia, ib; int matched; int numNonDialableCharsInA = 0; int numNonDialableCharsInB = 0; if (a == null || b == null) return a == b; if (a.length() == 0 || b.length() == 0) { return false; } ia = indexOfLastNetworkChar (a); ib = indexOfLastNetworkChar (b); matched = 0; while (ia >= 0 && ib >=0) { char ca, cb; boolean skipCmp = false; ca = a.charAt(ia); if (!isDialable(ca)) { ia--; skipCmp = true; numNonDialableCharsInA++; } cb = b.charAt(ib); if (!isDialable(cb)) { ib--; skipCmp = true; numNonDialableCharsInB++; } if (!skipCmp) { if (cb != ca && ca != WILD && cb != WILD) { break; } ia--; ib--; matched++; } } if (matched < MIN_MATCH) { int effectiveALen = a.length() - numNonDialableCharsInA; int effectiveBLen = b.length() - numNonDialableCharsInB; // if the number of dialable chars in a and b match, but the matched chars < MIN_MATCH, // treat them as equal (i.e. 404-04 and 40404) if (effectiveALen == effectiveBLen && effectiveALen == matched) { return true; } return false; } // At least one string has matched completely; if (matched >= MIN_MATCH && (ia < 0 || ib < 0)) { return true; } /* * Now, what remains must be one of the following for a * match: * * - a '+' on one and a '00' or a '011' on the other * - a '0' on one and a (+,00)<country code> on the other * (for this, a '0' and a '00' prefix would have succeeded above) */ if (matchIntlPrefix(a, ia + 1) && matchIntlPrefix (b, ib +1) ) { return true; } if (matchTrunkPrefix(a, ia + 1) && matchIntlPrefixAndCC(b, ib +1) ) { return true; } if (matchTrunkPrefix(b, ib + 1) && matchIntlPrefixAndCC(a, ia +1) ) { return true; } return false; }
@hide
/** * @hide */
public static boolean compareStrictly(String a, String b) { return compareStrictly(a, b, true); }
@hide
/** * @hide */
public static boolean compareStrictly(String a, String b, boolean acceptInvalidCCCPrefix) { if (a == null || b == null) { return a == b; } else if (a.length() == 0 && b.length() == 0) { return false; } int forwardIndexA = 0; int forwardIndexB = 0; CountryCallingCodeAndNewIndex cccA = tryGetCountryCallingCodeAndNewIndex(a, acceptInvalidCCCPrefix); CountryCallingCodeAndNewIndex cccB = tryGetCountryCallingCodeAndNewIndex(b, acceptInvalidCCCPrefix); boolean bothHasCountryCallingCode = false; boolean okToIgnorePrefix = true; boolean trunkPrefixIsOmittedA = false; boolean trunkPrefixIsOmittedB = false; if (cccA != null && cccB != null) { if (cccA.countryCallingCode != cccB.countryCallingCode) { // Different Country Calling Code. Must be different phone number. return false; } // When both have ccc, do not ignore trunk prefix. Without this, // "+81123123" becomes same as "+810123123" (+81 == Japan) okToIgnorePrefix = false; bothHasCountryCallingCode = true; forwardIndexA = cccA.newIndex; forwardIndexB = cccB.newIndex; } else if (cccA == null && cccB == null) { // When both do not have ccc, do not ignore trunk prefix. Without this, // "123123" becomes same as "0123123" okToIgnorePrefix = false; } else { if (cccA != null) { forwardIndexA = cccA.newIndex; } else { int tmp = tryGetTrunkPrefixOmittedIndex(b, 0); if (tmp >= 0) { forwardIndexA = tmp; trunkPrefixIsOmittedA = true; } } if (cccB != null) { forwardIndexB = cccB.newIndex; } else { int tmp = tryGetTrunkPrefixOmittedIndex(b, 0); if (tmp >= 0) { forwardIndexB = tmp; trunkPrefixIsOmittedB = true; } } } int backwardIndexA = a.length() - 1; int backwardIndexB = b.length() - 1; while (backwardIndexA >= forwardIndexA && backwardIndexB >= forwardIndexB) { boolean skip_compare = false; final char chA = a.charAt(backwardIndexA); final char chB = b.charAt(backwardIndexB); if (isSeparator(chA)) { backwardIndexA--; skip_compare = true; } if (isSeparator(chB)) { backwardIndexB--; skip_compare = true; } if (!skip_compare) { if (chA != chB) { return false; } backwardIndexA--; backwardIndexB--; } } if (okToIgnorePrefix) { if ((trunkPrefixIsOmittedA && forwardIndexA <= backwardIndexA) || !checkPrefixIsIgnorable(a, forwardIndexA, backwardIndexA)) { if (acceptInvalidCCCPrefix) { // Maybe the code handling the special case for Thailand makes the // result garbled, so disable the code and try again. // e.g. "16610001234" must equal to "6610001234", but with // Thailand-case handling code, they become equal to each other. // // Note: we select simplicity rather than adding some complicated // logic here for performance(like "checking whether remaining // numbers are just 66 or not"), assuming inputs are small // enough. return compare(a, b, false); } else { return false; } } if ((trunkPrefixIsOmittedB && forwardIndexB <= backwardIndexB) || !checkPrefixIsIgnorable(b, forwardIndexA, backwardIndexB)) { if (acceptInvalidCCCPrefix) { return compare(a, b, false); } else { return false; } } } else { // In the US, 1-650-555-1234 must be equal to 650-555-1234, // while 090-1234-1234 must not be equal to 90-1234-1234 in Japan. // This request exists just in US (with 1 trunk (NDD) prefix). // In addition, "011 11 7005554141" must not equal to "+17005554141", // while "011 1 7005554141" must equal to "+17005554141" // // In this comparison, we ignore the prefix '1' just once, when // - at least either does not have CCC, or // - the remaining non-separator number is 1 boolean maybeNamp = !bothHasCountryCallingCode; while (backwardIndexA >= forwardIndexA) { final char chA = a.charAt(backwardIndexA); if (isDialable(chA)) { if (maybeNamp && tryGetISODigit(chA) == 1) { maybeNamp = false; } else { return false; } } backwardIndexA--; } while (backwardIndexB >= forwardIndexB) { final char chB = b.charAt(backwardIndexB); if (isDialable(chB)) { if (maybeNamp && tryGetISODigit(chB) == 1) { maybeNamp = false; } else { return false; } } backwardIndexB--; } } return true; }
Returns the rightmost MIN_MATCH (5) characters in the network portion in *reversed* order This can be used to do a database lookup against the column that stores getStrippedReversed() Returns null if phoneNumber == null
/** * Returns the rightmost MIN_MATCH (5) characters in the network portion * in *reversed* order * * This can be used to do a database lookup against the column * that stores getStrippedReversed() * * Returns null if phoneNumber == null */
public static String toCallerIDMinMatch(String phoneNumber) { String np = extractNetworkPortionAlt(phoneNumber); return internalGetStrippedReversed(np, MIN_MATCH); }
Returns the network portion reversed. This string is intended to go into an index column for a database lookup. Returns null if phoneNumber == null
/** * Returns the network portion reversed. * This string is intended to go into an index column for a * database lookup. * * Returns null if phoneNumber == null */
public static String getStrippedReversed(String phoneNumber) { String np = extractNetworkPortionAlt(phoneNumber); if (np == null) return null; return internalGetStrippedReversed(np, np.length()); }
Returns the last numDigits of the reversed phone number Returns null if np == null
/** * Returns the last numDigits of the reversed phone number * Returns null if np == null */
private static String internalGetStrippedReversed(String np, int numDigits) { if (np == null) return null; StringBuilder ret = new StringBuilder(numDigits); int length = np.length(); for (int i = length - 1, s = length ; i >= 0 && (s - i) <= numDigits ; i-- ) { char c = np.charAt(i); ret.append(c); } return ret.toString(); }
Basically: makes sure there's a + in front of a TOA_International number Returns null if s == null
/** * Basically: makes sure there's a + in front of a * TOA_International number * * Returns null if s == null */
public static String stringFromStringAndTOA(String s, int TOA) { if (s == null) return null; if (TOA == TOA_International && s.length() > 0 && s.charAt(0) != '+') { return "+" + s; } return s; }
Returns the TOA for the given dial string Basically, returns TOA_International if there's a + prefix
/** * Returns the TOA for the given dial string * Basically, returns TOA_International if there's a + prefix */
public static int toaFromString(String s) { if (s != null && s.length() > 0 && s.charAt(0) == '+') { return TOA_International; } return TOA_Unknown; }
3GPP TS 24.008 10.5.4.7 Called Party BCD Number See Also TS 51.011 10.5.1 "dialing number/ssc string" and TS 11.11 "10.3.1 EF adn (Abbreviated dialing numbers)"
Params:
  • bytes – the data buffer
  • offset – should point to the TOA (aka. TON/NPI) octet after the length byte
  • length – is the number of bytes including TOA byte and must be at least 2
Returns:partial string on invalid decode
Deprecated:use calledPartyBCDToString(byte[], int, int, int) instead. Calling this method is equivalent to calling calledPartyBCDToString(byte[], int, int) with BCD_EXTENDED_TYPE_EF_ADN as the extended type.
/** * 3GPP TS 24.008 10.5.4.7 * Called Party BCD Number * * See Also TS 51.011 10.5.1 "dialing number/ssc string" * and TS 11.11 "10.3.1 EF adn (Abbreviated dialing numbers)" * * @param bytes the data buffer * @param offset should point to the TOA (aka. TON/NPI) octet after the length byte * @param length is the number of bytes including TOA byte * and must be at least 2 * * @return partial string on invalid decode * * @deprecated use {@link #calledPartyBCDToString(byte[], int, int, int)} instead. Calling this * method is equivalent to calling {@link #calledPartyBCDToString(byte[], int, int)} with * {@link #BCD_EXTENDED_TYPE_EF_ADN} as the extended type. */
@Deprecated public static String calledPartyBCDToString(byte[] bytes, int offset, int length) { return calledPartyBCDToString(bytes, offset, length, BCD_EXTENDED_TYPE_EF_ADN); }
3GPP TS 24.008 10.5.4.7 Called Party BCD Number See Also TS 51.011 10.5.1 "dialing number/ssc string" and TS 11.11 "10.3.1 EF adn (Abbreviated dialing numbers)"
Params:
  • bytes – the data buffer
  • offset – should point to the TOA (aka. TON/NPI) octet after the length byte
  • length – is the number of bytes including TOA byte and must be at least 2
  • bcdExtType – used to determine the extended bcd coding
See Also:
/** * 3GPP TS 24.008 10.5.4.7 * Called Party BCD Number * * See Also TS 51.011 10.5.1 "dialing number/ssc string" * and TS 11.11 "10.3.1 EF adn (Abbreviated dialing numbers)" * * @param bytes the data buffer * @param offset should point to the TOA (aka. TON/NPI) octet after the length byte * @param length is the number of bytes including TOA byte * and must be at least 2 * @param bcdExtType used to determine the extended bcd coding * @see #BCD_EXTENDED_TYPE_EF_ADN * @see #BCD_EXTENDED_TYPE_CALLED_PARTY * */
public static String calledPartyBCDToString( byte[] bytes, int offset, int length, @BcdExtendType int bcdExtType) { boolean prependPlus = false; StringBuilder ret = new StringBuilder(1 + length * 2); if (length < 2) { return ""; } //Only TON field should be taken in consideration if ((bytes[offset] & 0xf0) == (TOA_International & 0xf0)) { prependPlus = true; } internalCalledPartyBCDFragmentToString( ret, bytes, offset + 1, length - 1, bcdExtType); if (prependPlus && ret.length() == 0) { // If the only thing there is a prepended plus, return "" return ""; } if (prependPlus) { // This is an "international number" and should have // a plus prepended to the dialing number. But there // can also be GSM MMI codes as defined in TS 22.030 6.5.2 // so we need to handle those also. // // http://web.telia.com/~u47904776/gsmkode.htm // has a nice list of some of these GSM codes. // // Examples are: // **21*+886988171479# // **21*8311234567# // *21# // #21# // *#21# // *31#+11234567890 // #31#+18311234567 // #31#8311234567 // 18311234567 // +18311234567# // +18311234567 // Odd ball cases that some phones handled // where there is no dialing number so they // append the "+" // *21#+ // **21#+ String retString = ret.toString(); Pattern p = Pattern.compile("(^[#*])(.*)([#*])(.*)(#)$"); Matcher m = p.matcher(retString); if (m.matches()) { if ("".equals(m.group(2))) { // Started with two [#*] ends with # // So no dialing number and we'll just // append a +, this handles **21#+ ret = new StringBuilder(); ret.append(m.group(1)); ret.append(m.group(3)); ret.append(m.group(4)); ret.append(m.group(5)); ret.append("+"); } else { // Starts with [#*] and ends with # // Assume group 4 is a dialing number // such as *21*+1234554# ret = new StringBuilder(); ret.append(m.group(1)); ret.append(m.group(2)); ret.append(m.group(3)); ret.append("+"); ret.append(m.group(4)); ret.append(m.group(5)); } } else { p = Pattern.compile("(^[#*])(.*)([#*])(.*)"); m = p.matcher(retString); if (m.matches()) { // Starts with [#*] and only one other [#*] // Assume the data after last [#*] is dialing // number (i.e. group 4) such as *31#+11234567890. // This also includes the odd ball *21#+ ret = new StringBuilder(); ret.append(m.group(1)); ret.append(m.group(2)); ret.append(m.group(3)); ret.append("+"); ret.append(m.group(4)); } else { // Does NOT start with [#*] just prepend '+' ret = new StringBuilder(); ret.append('+'); ret.append(retString); } } } return ret.toString(); } private static void internalCalledPartyBCDFragmentToString( StringBuilder sb, byte [] bytes, int offset, int length, @BcdExtendType int bcdExtType) { for (int i = offset ; i < length + offset ; i++) { byte b; char c; c = bcdToChar((byte)(bytes[i] & 0xf), bcdExtType); if (c == 0) { return; } sb.append(c); // FIXME(mkf) TS 23.040 9.1.2.3 says // "if a mobile receives 1111 in a position prior to // the last semi-octet then processing shall commence with // the next semi-octet and the intervening // semi-octet shall be ignored" // How does this jive with 24.008 10.5.4.7 b = (byte)((bytes[i] >> 4) & 0xf); if (b == 0xf && i + 1 == length + offset) { //ignore final 0xf break; } c = bcdToChar(b, bcdExtType); if (c == 0) { return; } sb.append(c); } }
Like calledPartyBCDToString, but field does not start with a TOA byte. For example: SIM ADN extension fields
Deprecated:use calledPartyBCDFragmentToString(byte[], int, int, int) instead. Calling this method is equivalent to calling calledPartyBCDFragmentToString(byte[], int, int, int) with BCD_EXTENDED_TYPE_EF_ADN as the extended type.
/** * Like calledPartyBCDToString, but field does not start with a * TOA byte. For example: SIM ADN extension fields * * @deprecated use {@link #calledPartyBCDFragmentToString(byte[], int, int, int)} instead. * Calling this method is equivalent to calling * {@link #calledPartyBCDFragmentToString(byte[], int, int, int)} with * {@link #BCD_EXTENDED_TYPE_EF_ADN} as the extended type. */
@Deprecated public static String calledPartyBCDFragmentToString(byte[] bytes, int offset, int length) { return calledPartyBCDFragmentToString(bytes, offset, length, BCD_EXTENDED_TYPE_EF_ADN); }
Like calledPartyBCDToString, but field does not start with a TOA byte. For example: SIM ADN extension fields
/** * Like calledPartyBCDToString, but field does not start with a * TOA byte. For example: SIM ADN extension fields */
public static String calledPartyBCDFragmentToString( byte[] bytes, int offset, int length, @BcdExtendType int bcdExtType) { StringBuilder ret = new StringBuilder(length * 2); internalCalledPartyBCDFragmentToString(ret, bytes, offset, length, bcdExtType); return ret.toString(); }
Returns the correspond character for given b based on bcdExtType, or 0 on invalid code.
/** * Returns the correspond character for given {@code b} based on {@code bcdExtType}, or 0 on * invalid code. */
private static char bcdToChar(byte b, @BcdExtendType int bcdExtType) { if (b < 0xa) { return (char) ('0' + b); } String extended = null; if (BCD_EXTENDED_TYPE_EF_ADN == bcdExtType) { extended = BCD_EF_ADN_EXTENDED; } else if (BCD_EXTENDED_TYPE_CALLED_PARTY == bcdExtType) { extended = BCD_CALLED_PARTY_EXTENDED; } if (extended == null || b - 0xa >= extended.length()) { return 0; } return extended.charAt(b - 0xa); } private static int charToBCD(char c, @BcdExtendType int bcdExtType) { if ('0' <= c && c <= '9') { return c - '0'; } String extended = null; if (BCD_EXTENDED_TYPE_EF_ADN == bcdExtType) { extended = BCD_EF_ADN_EXTENDED; } else if (BCD_EXTENDED_TYPE_CALLED_PARTY == bcdExtType) { extended = BCD_CALLED_PARTY_EXTENDED; } if (extended == null || extended.indexOf(c) == -1) { throw new RuntimeException("invalid char for BCD " + c); } return 0xa + extended.indexOf(c); }
Return true iff the network portion of address is, as far as we can tell on the device, suitable for use as an SMS destination address.
/** * Return true iff the network portion of <code>address</code> is, * as far as we can tell on the device, suitable for use as an SMS * destination address. */
public static boolean isWellFormedSmsAddress(String address) { String networkPortion = PhoneNumberUtils.extractNetworkPortion(address); return (!(networkPortion.equals("+") || TextUtils.isEmpty(networkPortion))) && isDialable(networkPortion); } public static boolean isGlobalPhoneNumber(String phoneNumber) { if (TextUtils.isEmpty(phoneNumber)) { return false; } Matcher match = GLOBAL_PHONE_NUMBER_PATTERN.matcher(phoneNumber); return match.matches(); } private static boolean isDialable(String address) { for (int i = 0, count = address.length(); i < count; i++) { if (!isDialable(address.charAt(i))) { return false; } } return true; } private static boolean isNonSeparator(String address) { for (int i = 0, count = address.length(); i < count; i++) { if (!isNonSeparator(address.charAt(i))) { return false; } } return true; }
Note: calls extractNetworkPortion(), so do not use for SIM EF[ADN] style records Returns null if network portion is empty.
/** * Note: calls extractNetworkPortion(), so do not use for * SIM EF[ADN] style records * * Returns null if network portion is empty. */
public static byte[] networkPortionToCalledPartyBCD(String s) { String networkPortion = extractNetworkPortion(s); return numberToCalledPartyBCDHelper( networkPortion, false, BCD_EXTENDED_TYPE_EF_ADN); }
Same as networkPortionToCalledPartyBCD, but includes a one-byte length prefix.
/** * Same as {@link #networkPortionToCalledPartyBCD}, but includes a * one-byte length prefix. */
public static byte[] networkPortionToCalledPartyBCDWithLength(String s) { String networkPortion = extractNetworkPortion(s); return numberToCalledPartyBCDHelper( networkPortion, true, BCD_EXTENDED_TYPE_EF_ADN); }
Convert a dialing number to BCD byte array
Params:
  • number – dialing number string. If the dialing number starts with '+', set to international TOA
Returns:BCD byte array
Deprecated:use numberToCalledPartyBCD(String, int) instead. Calling this method is equivalent to calling numberToCalledPartyBCD(String, int) with BCD_EXTENDED_TYPE_EF_ADN as the extended type.
/** * Convert a dialing number to BCD byte array * * @param number dialing number string. If the dialing number starts with '+', set to * international TOA * * @return BCD byte array * * @deprecated use {@link #numberToCalledPartyBCD(String, int)} instead. Calling this method * is equivalent to calling {@link #numberToCalledPartyBCD(String, int)} with * {@link #BCD_EXTENDED_TYPE_EF_ADN} as the extended type. */
@Deprecated public static byte[] numberToCalledPartyBCD(String number) { return numberToCalledPartyBCD(number, BCD_EXTENDED_TYPE_EF_ADN); }
Convert a dialing number to BCD byte array
Params:
  • number – dialing number string. If the dialing number starts with '+', set to international TOA
  • bcdExtType – used to determine the extended bcd coding
See Also:
Returns:BCD byte array
/** * Convert a dialing number to BCD byte array * * @param number dialing number string. If the dialing number starts with '+', set to * international TOA * @param bcdExtType used to determine the extended bcd coding * @see #BCD_EXTENDED_TYPE_EF_ADN * @see #BCD_EXTENDED_TYPE_CALLED_PARTY * * @return BCD byte array */
public static byte[] numberToCalledPartyBCD(String number, @BcdExtendType int bcdExtType) { return numberToCalledPartyBCDHelper(number, false, bcdExtType); }
If includeLength is true, prepend a one-byte length value to the return array.
/** * If includeLength is true, prepend a one-byte length value to * the return array. */
private static byte[] numberToCalledPartyBCDHelper( String number, boolean includeLength, @BcdExtendType int bcdExtType) { int numberLenReal = number.length(); int numberLenEffective = numberLenReal; boolean hasPlus = number.indexOf('+') != -1; if (hasPlus) numberLenEffective--; if (numberLenEffective == 0) return null; int resultLen = (numberLenEffective + 1) / 2; // Encoded numbers require only 4 bits each. int extraBytes = 1; // Prepended TOA byte. if (includeLength) extraBytes++; // Optional prepended length byte. resultLen += extraBytes; byte[] result = new byte[resultLen]; int digitCount = 0; for (int i = 0; i < numberLenReal; i++) { char c = number.charAt(i); if (c == '+') continue; int shift = ((digitCount & 0x01) == 1) ? 4 : 0; result[extraBytes + (digitCount >> 1)] |= (byte)((charToBCD(c, bcdExtType) & 0x0F) << shift); digitCount++; } // 1-fill any trailing odd nibble/quartet. if ((digitCount & 0x01) == 1) result[extraBytes + (digitCount >> 1)] |= 0xF0; int offset = 0; if (includeLength) result[offset++] = (byte)(resultLen - 1); result[offset] = (byte)(hasPlus ? TOA_International : TOA_Unknown); return result; } //================ Number formatting =========================
The current locale is unknown, look for a country code or don't format
/** The current locale is unknown, look for a country code or don't format */
public static final int FORMAT_UNKNOWN = 0;
NANP formatting
/** NANP formatting */
public static final int FORMAT_NANP = 1;
Japanese formatting
/** Japanese formatting */
public static final int FORMAT_JAPAN = 2;
List of country codes for countries that use the NANP
/** List of country codes for countries that use the NANP */
private static final String[] NANP_COUNTRIES = new String[] { "US", // United States "CA", // Canada "AS", // American Samoa "AI", // Anguilla "AG", // Antigua and Barbuda "BS", // Bahamas "BB", // Barbados "BM", // Bermuda "VG", // British Virgin Islands "KY", // Cayman Islands "DM", // Dominica "DO", // Dominican Republic "GD", // Grenada "GU", // Guam "JM", // Jamaica "PR", // Puerto Rico "MS", // Montserrat "MP", // Northern Mariana Islands "KN", // Saint Kitts and Nevis "LC", // Saint Lucia "VC", // Saint Vincent and the Grenadines "TT", // Trinidad and Tobago "TC", // Turks and Caicos Islands "VI", // U.S. Virgin Islands }; private static final String KOREA_ISO_COUNTRY_CODE = "KR"; private static final String JAPAN_ISO_COUNTRY_CODE = "JP";
Breaks the given number down and formats it according to the rules for the country the number is from.
Params:
  • source – The phone number to format
Returns:A locally acceptable formatting of the input, or the raw input if formatting rules aren't known for the number
Deprecated:Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead
/** * Breaks the given number down and formats it according to the rules * for the country the number is from. * * @param source The phone number to format * @return A locally acceptable formatting of the input, or the raw input if * formatting rules aren't known for the number * * @deprecated Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead */
@Deprecated public static String formatNumber(String source) { SpannableStringBuilder text = new SpannableStringBuilder(source); formatNumber(text, getFormatTypeForLocale(Locale.getDefault())); return text.toString(); }
Formats the given number with the given formatting type. Currently FORMAT_NANP and FORMAT_JAPAN are supported as a formating type.
Params:
  • source – the phone number to format
  • defaultFormattingType – The default formatting rules to apply if the number does not begin with +[country_code]
Returns:The phone number formatted with the given formatting type.
@hide
Deprecated:Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead
/** * Formats the given number with the given formatting type. Currently * {@link #FORMAT_NANP} and {@link #FORMAT_JAPAN} are supported as a formating type. * * @param source the phone number to format * @param defaultFormattingType The default formatting rules to apply if the number does * not begin with +[country_code] * @return The phone number formatted with the given formatting type. * * @hide * @deprecated Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead */
@Deprecated public static String formatNumber(String source, int defaultFormattingType) { SpannableStringBuilder text = new SpannableStringBuilder(source); formatNumber(text, defaultFormattingType); return text.toString(); }
Returns the phone number formatting type for the given locale.
Params:
Returns:The formatting type for the given locale, or FORMAT_UNKNOWN if the formatting rules are not known for the given locale
Deprecated:Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead
/** * Returns the phone number formatting type for the given locale. * * @param locale The locale of interest, usually {@link Locale#getDefault()} * @return The formatting type for the given locale, or FORMAT_UNKNOWN if the formatting * rules are not known for the given locale * * @deprecated Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead */
@Deprecated public static int getFormatTypeForLocale(Locale locale) { String country = locale.getCountry(); return getFormatTypeFromCountryCode(country); }
Formats a phone number in-place. Currently FORMAT_JAPAN and FORMAT_NANP is supported as a second argument.
Params:
  • text – The number to be formatted, will be modified with the formatting
  • defaultFormattingType – The default formatting rules to apply if the number does not begin with +[country_code]
Deprecated:Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead
/** * Formats a phone number in-place. Currently {@link #FORMAT_JAPAN} and {@link #FORMAT_NANP} * is supported as a second argument. * * @param text The number to be formatted, will be modified with the formatting * @param defaultFormattingType The default formatting rules to apply if the number does * not begin with +[country_code] * * @deprecated Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead */
@Deprecated public static void formatNumber(Editable text, int defaultFormattingType) { int formatType = defaultFormattingType; if (text.length() > 2 && text.charAt(0) == '+') { if (text.charAt(1) == '1') { formatType = FORMAT_NANP; } else if (text.length() >= 3 && text.charAt(1) == '8' && text.charAt(2) == '1') { formatType = FORMAT_JAPAN; } else { formatType = FORMAT_UNKNOWN; } } switch (formatType) { case FORMAT_NANP: formatNanpNumber(text); return; case FORMAT_JAPAN: formatJapaneseNumber(text); return; case FORMAT_UNKNOWN: removeDashes(text); return; } } private static final int NANP_STATE_DIGIT = 1; private static final int NANP_STATE_PLUS = 2; private static final int NANP_STATE_ONE = 3; private static final int NANP_STATE_DASH = 4;
Formats a phone number in-place using the NANP formatting rules. Numbers will be formatted as:

xxxxx xxx-xxxx xxx-xxx-xxxx 1-xxx-xxx-xxxx +1-xxx-xxx-xxxx

Params:
  • text – the number to be formatted, will be modified with the formatting
Deprecated:Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead
/** * Formats a phone number in-place using the NANP formatting rules. Numbers will be formatted * as: * * <p><code> * xxxxx * xxx-xxxx * xxx-xxx-xxxx * 1-xxx-xxx-xxxx * +1-xxx-xxx-xxxx * </code></p> * * @param text the number to be formatted, will be modified with the formatting * * @deprecated Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead */
@Deprecated public static void formatNanpNumber(Editable text) { int length = text.length(); if (length > "+1-nnn-nnn-nnnn".length()) { // The string is too long to be formatted return; } else if (length <= 5) { // The string is either a shortcode or too short to be formatted return; } CharSequence saved = text.subSequence(0, length); // Strip the dashes first, as we're going to add them back removeDashes(text); length = text.length(); // When scanning the number we record where dashes need to be added, // if they're non-0 at the end of the scan the dashes will be added in // the proper places. int dashPositions[] = new int[3]; int numDashes = 0; int state = NANP_STATE_DIGIT; int numDigits = 0; for (int i = 0; i < length; i++) { char c = text.charAt(i); switch (c) { case '1': if (numDigits == 0 || state == NANP_STATE_PLUS) { state = NANP_STATE_ONE; break; } // fall through case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '0': if (state == NANP_STATE_PLUS) { // Only NANP number supported for now text.replace(0, length, saved); return; } else if (state == NANP_STATE_ONE) { // Found either +1 or 1, follow it up with a dash dashPositions[numDashes++] = i; } else if (state != NANP_STATE_DASH && (numDigits == 3 || numDigits == 6)) { // Found a digit that should be after a dash that isn't dashPositions[numDashes++] = i; } state = NANP_STATE_DIGIT; numDigits++; break; case '-': state = NANP_STATE_DASH; break; case '+': if (i == 0) { // Plus is only allowed as the first character state = NANP_STATE_PLUS; break; } // Fall through default: // Unknown character, bail on formatting text.replace(0, length, saved); return; } } if (numDigits == 7) { // With 7 digits we want xxx-xxxx, not xxx-xxx-x numDashes--; } // Actually put the dashes in place for (int i = 0; i < numDashes; i++) { int pos = dashPositions[i]; text.replace(pos + i, pos + i, "-"); } // Remove trailing dashes int len = text.length(); while (len > 0) { if (text.charAt(len - 1) == '-') { text.delete(len - 1, len); len--; } else { break; } } }
Formats a phone number in-place using the Japanese formatting rules. Numbers will be formatted as:

03-xxxx-xxxx 090-xxxx-xxxx 0120-xxx-xxx +81-3-xxxx-xxxx +81-90-xxxx-xxxx

Params:
  • text – the number to be formatted, will be modified with the formatting
Deprecated:Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead
/** * Formats a phone number in-place using the Japanese formatting rules. * Numbers will be formatted as: * * <p><code> * 03-xxxx-xxxx * 090-xxxx-xxxx * 0120-xxx-xxx * +81-3-xxxx-xxxx * +81-90-xxxx-xxxx * </code></p> * * @param text the number to be formatted, will be modified with * the formatting * * @deprecated Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead */
@Deprecated public static void formatJapaneseNumber(Editable text) { JapanesePhoneNumberFormatter.format(text); }
Removes all dashes from the number.
Params:
  • text – the number to clear from dashes
/** * Removes all dashes from the number. * * @param text the number to clear from dashes */
private static void removeDashes(Editable text) { int p = 0; while (p < text.length()) { if (text.charAt(p) == '-') { text.delete(p, p + 1); } else { p++; } } }
Formats the specified phoneNumber to the E.164 representation.
Params:
  • phoneNumber – the phone number to format.
  • defaultCountryIso – the ISO 3166-1 two letters country code.
Returns:the E.164 representation, or null if the given phone number is not valid.
/** * Formats the specified {@code phoneNumber} to the E.164 representation. * * @param phoneNumber the phone number to format. * @param defaultCountryIso the ISO 3166-1 two letters country code. * @return the E.164 representation, or null if the given phone number is not valid. */
public static String formatNumberToE164(String phoneNumber, String defaultCountryIso) { return formatNumberInternal(phoneNumber, defaultCountryIso, PhoneNumberFormat.E164); }
Formats the specified phoneNumber to the RFC3966 representation.
Params:
  • phoneNumber – the phone number to format.
  • defaultCountryIso – the ISO 3166-1 two letters country code.
Returns:the RFC3966 representation, or null if the given phone number is not valid.
/** * Formats the specified {@code phoneNumber} to the RFC3966 representation. * * @param phoneNumber the phone number to format. * @param defaultCountryIso the ISO 3166-1 two letters country code. * @return the RFC3966 representation, or null if the given phone number is not valid. */
public static String formatNumberToRFC3966(String phoneNumber, String defaultCountryIso) { return formatNumberInternal(phoneNumber, defaultCountryIso, PhoneNumberFormat.RFC3966); }
Formats the raw phone number (string) using the specified formatIdentifier.

The given phone number must have an area code and could have a country code.

The defaultCountryIso is used to validate the given number and generate the formatted number if the specified number doesn't have a country code.

Params:
  • rawPhoneNumber – The phone number to format.
  • defaultCountryIso – The ISO 3166-1 two letters country code.
  • formatIdentifier – The (enum) identifier of the desired format.
Returns:the formatted representation, or null if the specified number is not valid.
/** * Formats the raw phone number (string) using the specified {@code formatIdentifier}. * <p> * The given phone number must have an area code and could have a country code. * <p> * The defaultCountryIso is used to validate the given number and generate the formatted number * if the specified number doesn't have a country code. * * @param rawPhoneNumber The phone number to format. * @param defaultCountryIso The ISO 3166-1 two letters country code. * @param formatIdentifier The (enum) identifier of the desired format. * @return the formatted representation, or null if the specified number is not valid. */
private static String formatNumberInternal( String rawPhoneNumber, String defaultCountryIso, PhoneNumberFormat formatIdentifier) { PhoneNumberUtil util = PhoneNumberUtil.getInstance(); try { PhoneNumber phoneNumber = util.parse(rawPhoneNumber, defaultCountryIso); if (util.isValidNumber(phoneNumber)) { return util.format(phoneNumber, formatIdentifier); } } catch (NumberParseException ignored) { } return null; }
Determines if a {@param phoneNumber} is international if dialed from {@param defaultCountryIso}.
Params:
  • phoneNumber – The phone number.
  • defaultCountryIso – The current country ISO.
Returns:true if the number is international, false otherwise.
@hide
/** * Determines if a {@param phoneNumber} is international if dialed from * {@param defaultCountryIso}. * * @param phoneNumber The phone number. * @param defaultCountryIso The current country ISO. * @return {@code true} if the number is international, {@code false} otherwise. * @hide */
public static boolean isInternationalNumber(String phoneNumber, String defaultCountryIso) { // If no phone number is provided, it can't be international. if (TextUtils.isEmpty(phoneNumber)) { return false; } // If it starts with # or * its not international. if (phoneNumber.startsWith("#") || phoneNumber.startsWith("*")) { return false; } PhoneNumberUtil util = PhoneNumberUtil.getInstance(); try { PhoneNumber pn = util.parseAndKeepRawInput(phoneNumber, defaultCountryIso); return pn.getCountryCode() != util.getCountryCodeForRegion(defaultCountryIso); } catch (NumberParseException e) { return false; } }
Format a phone number.

If the given number doesn't have the country code, the phone will be formatted to the default country's convention.

Params:
  • phoneNumber – the number to be formatted.
  • defaultCountryIso – the ISO 3166-1 two letters country code whose convention will be used if the given number doesn't have the country code.
Returns:the formatted number, or null if the given number is not valid.
/** * Format a phone number. * <p> * If the given number doesn't have the country code, the phone will be * formatted to the default country's convention. * * @param phoneNumber * the number to be formatted. * @param defaultCountryIso * the ISO 3166-1 two letters country code whose convention will * be used if the given number doesn't have the country code. * @return the formatted number, or null if the given number is not valid. */
public static String formatNumber(String phoneNumber, String defaultCountryIso) { // Do not attempt to format numbers that start with a hash or star symbol. if (phoneNumber.startsWith("#") || phoneNumber.startsWith("*")) { return phoneNumber; } PhoneNumberUtil util = PhoneNumberUtil.getInstance(); String result = null; try { PhoneNumber pn = util.parseAndKeepRawInput(phoneNumber, defaultCountryIso); if (KOREA_ISO_COUNTRY_CODE.equalsIgnoreCase(defaultCountryIso) && (pn.getCountryCode() == util.getCountryCodeForRegion(KOREA_ISO_COUNTRY_CODE)) && (pn.getCountryCodeSource() == PhoneNumber.CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN)) { /** * Need to reformat any local Korean phone numbers (when the user is in Korea) with * country code to corresponding national format which would replace the leading * +82 with 0. */ result = util.format(pn, PhoneNumberUtil.PhoneNumberFormat.NATIONAL); } else if (JAPAN_ISO_COUNTRY_CODE.equalsIgnoreCase(defaultCountryIso) && pn.getCountryCode() == util.getCountryCodeForRegion(JAPAN_ISO_COUNTRY_CODE) && (pn.getCountryCodeSource() == PhoneNumber.CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN)) { /** * Need to reformat Japanese phone numbers (when user is in Japan) with the national * dialing format. */ result = util.format(pn, PhoneNumberUtil.PhoneNumberFormat.NATIONAL); } else { result = util.formatInOriginalFormat(pn, defaultCountryIso); } } catch (NumberParseException e) { } return result; }
Format the phone number only if the given number hasn't been formatted.

The number which has only dailable character is treated as not being formatted.

Params:
  • phoneNumber – the number to be formatted.
  • phoneNumberE164 – the E164 format number whose country code is used if the given phoneNumber doesn't have the country code.
  • defaultCountryIso – the ISO 3166-1 two letters country code whose convention will be used if the phoneNumberE164 is null or invalid, or if phoneNumber contains IDD.
Returns:the formatted number if the given number has been formatted, otherwise, return the given number.
/** * Format the phone number only if the given number hasn't been formatted. * <p> * The number which has only dailable character is treated as not being * formatted. * * @param phoneNumber * the number to be formatted. * @param phoneNumberE164 * the E164 format number whose country code is used if the given * phoneNumber doesn't have the country code. * @param defaultCountryIso * the ISO 3166-1 two letters country code whose convention will * be used if the phoneNumberE164 is null or invalid, or if phoneNumber * contains IDD. * @return the formatted number if the given number has been formatted, * otherwise, return the given number. */
public static String formatNumber( String phoneNumber, String phoneNumberE164, String defaultCountryIso) { int len = phoneNumber.length(); for (int i = 0; i < len; i++) { if (!isDialable(phoneNumber.charAt(i))) { return phoneNumber; } } PhoneNumberUtil util = PhoneNumberUtil.getInstance(); // Get the country code from phoneNumberE164 if (phoneNumberE164 != null && phoneNumberE164.length() >= 2 && phoneNumberE164.charAt(0) == '+') { try { // The number to be parsed is in E164 format, so the default region used doesn't // matter. PhoneNumber pn = util.parse(phoneNumberE164, "ZZ"); String regionCode = util.getRegionCodeForNumber(pn); if (!TextUtils.isEmpty(regionCode) && // This makes sure phoneNumber doesn't contain an IDD normalizeNumber(phoneNumber).indexOf(phoneNumberE164.substring(1)) <= 0) { defaultCountryIso = regionCode; } } catch (NumberParseException e) { } } String result = formatNumber(phoneNumber, defaultCountryIso); return result != null ? result : phoneNumber; }
Normalize a phone number by removing the characters other than digits. If the given number has keypad letters, the letters will be converted to digits first.
Params:
  • phoneNumber – the number to be normalized.
Returns:the normalized number.
/** * Normalize a phone number by removing the characters other than digits. If * the given number has keypad letters, the letters will be converted to * digits first. * * @param phoneNumber the number to be normalized. * @return the normalized number. */
public static String normalizeNumber(String phoneNumber) { if (TextUtils.isEmpty(phoneNumber)) { return ""; } StringBuilder sb = new StringBuilder(); int len = phoneNumber.length(); for (int i = 0; i < len; i++) { char c = phoneNumber.charAt(i); // Character.digit() supports ASCII and Unicode digits (fullwidth, Arabic-Indic, etc.) int digit = Character.digit(c, 10); if (digit != -1) { sb.append(digit); } else if (sb.length() == 0 && c == '+') { sb.append(c); } else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { return normalizeNumber(PhoneNumberUtils.convertKeypadLettersToDigits(phoneNumber)); } } return sb.toString(); }
Replaces all unicode(e.g. Arabic, Persian) digits with their decimal digit equivalents.
Params:
  • number – the number to perform the replacement on.
Returns:the replaced number.
/** * Replaces all unicode(e.g. Arabic, Persian) digits with their decimal digit equivalents. * * @param number the number to perform the replacement on. * @return the replaced number. */
public static String replaceUnicodeDigits(String number) { StringBuilder normalizedDigits = new StringBuilder(number.length()); for (char c : number.toCharArray()) { int digit = Character.digit(c, 10); if (digit != -1) { normalizedDigits.append(digit); } else { normalizedDigits.append(c); } } return normalizedDigits.toString(); } // Three and four digit phone numbers for either special services, // or 3-6 digit addresses from the network (eg carrier-originated SMS messages) should // not match. // // This constant used to be 5, but SMS short codes has increased in length and // can be easily 6 digits now days. Most countries have SMS short code length between // 3 to 6 digits. The exceptions are // // Australia: Short codes are six or eight digits in length, starting with the prefix "19" // followed by an additional four or six digits and two. // Czechia: Codes are seven digits in length for MO and five (not billed) or // eight (billed) for MT direction // // see http://en.wikipedia.org/wiki/Short_code#Regional_differences for reference // // However, in order to loose match 650-555-1212 and 555-1212, we need to set the min match // to 7. static final int MIN_MATCH = 7;
Checks a given number against the list of emergency numbers provided by the RIL and SIM card.
Params:
  • number – the number to look up.
Returns:true if the number is in the list of emergency numbers listed in the RIL / SIM, otherwise return false.
/** * Checks a given number against the list of * emergency numbers provided by the RIL and SIM card. * * @param number the number to look up. * @return true if the number is in the list of emergency numbers * listed in the RIL / SIM, otherwise return false. */
public static boolean isEmergencyNumber(String number) { return isEmergencyNumber(getDefaultVoiceSubId(), number); }
Checks a given number against the list of emergency numbers provided by the RIL and SIM card.
Params:
  • subId – the subscription id of the SIM.
  • number – the number to look up.
Returns:true if the number is in the list of emergency numbers listed in the RIL / SIM, otherwise return false.
@hide
/** * Checks a given number against the list of * emergency numbers provided by the RIL and SIM card. * * @param subId the subscription id of the SIM. * @param number the number to look up. * @return true if the number is in the list of emergency numbers * listed in the RIL / SIM, otherwise return false. * @hide */
public static boolean isEmergencyNumber(int subId, String number) { // Return true only if the specified number *exactly* matches // one of the emergency numbers listed by the RIL / SIM. return isEmergencyNumberInternal(subId, number, true /* useExactMatch */); }
Checks if given number might *potentially* result in a call to an emergency service on the current network. Specifically, this method will return true if the specified number is an emergency number according to the list managed by the RIL or SIM, *or* if the specified number simply starts with the same digits as any of the emergency numbers listed in the RIL / SIM. This method is intended for internal use by the phone app when deciding whether to allow ACTION_CALL intents from 3rd party apps (where we're required to *not* allow emergency calls to be placed.)
Params:
  • number – the number to look up.
Returns:true if the number is in the list of emergency numbers listed in the RIL / SIM, *or* if the number starts with the same digits as any of those emergency numbers.
@hide
/** * Checks if given number might *potentially* result in * a call to an emergency service on the current network. * * Specifically, this method will return true if the specified number * is an emergency number according to the list managed by the RIL or * SIM, *or* if the specified number simply starts with the same * digits as any of the emergency numbers listed in the RIL / SIM. * * This method is intended for internal use by the phone app when * deciding whether to allow ACTION_CALL intents from 3rd party apps * (where we're required to *not* allow emergency calls to be placed.) * * @param number the number to look up. * @return true if the number is in the list of emergency numbers * listed in the RIL / SIM, *or* if the number starts with the * same digits as any of those emergency numbers. * * @hide */
public static boolean isPotentialEmergencyNumber(String number) { return isPotentialEmergencyNumber(getDefaultVoiceSubId(), number); }
Checks if given number might *potentially* result in a call to an emergency service on the current network. Specifically, this method will return true if the specified number is an emergency number according to the list managed by the RIL or SIM, *or* if the specified number simply starts with the same digits as any of the emergency numbers listed in the RIL / SIM. This method is intended for internal use by the phone app when deciding whether to allow ACTION_CALL intents from 3rd party apps (where we're required to *not* allow emergency calls to be placed.)
Params:
  • subId – the subscription id of the SIM.
  • number – the number to look up.
Returns:true if the number is in the list of emergency numbers listed in the RIL / SIM, *or* if the number starts with the same digits as any of those emergency numbers.
@hide
/** * Checks if given number might *potentially* result in * a call to an emergency service on the current network. * * Specifically, this method will return true if the specified number * is an emergency number according to the list managed by the RIL or * SIM, *or* if the specified number simply starts with the same * digits as any of the emergency numbers listed in the RIL / SIM. * * This method is intended for internal use by the phone app when * deciding whether to allow ACTION_CALL intents from 3rd party apps * (where we're required to *not* allow emergency calls to be placed.) * * @param subId the subscription id of the SIM. * @param number the number to look up. * @return true if the number is in the list of emergency numbers * listed in the RIL / SIM, *or* if the number starts with the * same digits as any of those emergency numbers. * @hide */
public static boolean isPotentialEmergencyNumber(int subId, String number) { // Check against the emergency numbers listed by the RIL / SIM, // and *don't* require an exact match. return isEmergencyNumberInternal(subId, number, false /* useExactMatch */); }
Helper function for isEmergencyNumber(String) and isPotentialEmergencyNumber(String).
Params:
  • number – the number to look up.
  • useExactMatch – if true, consider a number to be an emergency number only if it *exactly* matches a number listed in the RIL / SIM. If false, a number is considered to be an emergency number if it simply starts with the same digits as any of the emergency numbers listed in the RIL / SIM. (Setting useExactMatch to false allows you to identify number that could *potentially* result in emergency calls since many networks will actually ignore trailing digits after a valid emergency number.)
Returns:true if the number is in the list of emergency numbers listed in the RIL / sim, otherwise return false.
/** * Helper function for isEmergencyNumber(String) and * isPotentialEmergencyNumber(String). * * @param number the number to look up. * * @param useExactMatch if true, consider a number to be an emergency * number only if it *exactly* matches a number listed in * the RIL / SIM. If false, a number is considered to be an * emergency number if it simply starts with the same digits * as any of the emergency numbers listed in the RIL / SIM. * (Setting useExactMatch to false allows you to identify * number that could *potentially* result in emergency calls * since many networks will actually ignore trailing digits * after a valid emergency number.) * * @return true if the number is in the list of emergency numbers * listed in the RIL / sim, otherwise return false. */
private static boolean isEmergencyNumberInternal(String number, boolean useExactMatch) { return isEmergencyNumberInternal(getDefaultVoiceSubId(), number, useExactMatch); }
Helper function for isEmergencyNumber(String) and isPotentialEmergencyNumber(String).
Params:
  • subId – the subscription id of the SIM.
  • number – the number to look up.
  • useExactMatch – if true, consider a number to be an emergency number only if it *exactly* matches a number listed in the RIL / SIM. If false, a number is considered to be an emergency number if it simply starts with the same digits as any of the emergency numbers listed in the RIL / SIM. (Setting useExactMatch to false allows you to identify number that could *potentially* result in emergency calls since many networks will actually ignore trailing digits after a valid emergency number.)
Returns:true if the number is in the list of emergency numbers listed in the RIL / sim, otherwise return false.
/** * Helper function for isEmergencyNumber(String) and * isPotentialEmergencyNumber(String). * * @param subId the subscription id of the SIM. * @param number the number to look up. * * @param useExactMatch if true, consider a number to be an emergency * number only if it *exactly* matches a number listed in * the RIL / SIM. If false, a number is considered to be an * emergency number if it simply starts with the same digits * as any of the emergency numbers listed in the RIL / SIM. * (Setting useExactMatch to false allows you to identify * number that could *potentially* result in emergency calls * since many networks will actually ignore trailing digits * after a valid emergency number.) * * @return true if the number is in the list of emergency numbers * listed in the RIL / sim, otherwise return false. */
private static boolean isEmergencyNumberInternal(int subId, String number, boolean useExactMatch) { return isEmergencyNumberInternal(subId, number, null, useExactMatch); }
Checks if a given number is an emergency number for a specific country.
Params:
  • number – the number to look up.
  • defaultCountryIso – the specific country which the number should be checked against
Returns:if the number is an emergency number for the specific country, then return true, otherwise false
@hide
/** * Checks if a given number is an emergency number for a specific country. * * @param number the number to look up. * @param defaultCountryIso the specific country which the number should be checked against * @return if the number is an emergency number for the specific country, then return true, * otherwise false * * @hide */
public static boolean isEmergencyNumber(String number, String defaultCountryIso) { return isEmergencyNumber(getDefaultVoiceSubId(), number, defaultCountryIso); }
Checks if a given number is an emergency number for a specific country.
Params:
  • subId – the subscription id of the SIM.
  • number – the number to look up.
  • defaultCountryIso – the specific country which the number should be checked against
Returns:if the number is an emergency number for the specific country, then return true, otherwise false
@hide
/** * Checks if a given number is an emergency number for a specific country. * * @param subId the subscription id of the SIM. * @param number the number to look up. * @param defaultCountryIso the specific country which the number should be checked against * @return if the number is an emergency number for the specific country, then return true, * otherwise false * @hide */
public static boolean isEmergencyNumber(int subId, String number, String defaultCountryIso) { return isEmergencyNumberInternal(subId, number, defaultCountryIso, true /* useExactMatch */); }
Checks if a given number might *potentially* result in a call to an emergency service, for a specific country. Specifically, this method will return true if the specified number is an emergency number in the specified country, *or* if the number simply starts with the same digits as any emergency number for that country. This method is intended for internal use by the phone app when deciding whether to allow ACTION_CALL intents from 3rd party apps (where we're required to *not* allow emergency calls to be placed.)
Params:
  • number – the number to look up.
  • defaultCountryIso – the specific country which the number should be checked against
Returns:true if the number is an emergency number for the specific country, *or* if the number starts with the same digits as any of those emergency numbers.
@hide
/** * Checks if a given number might *potentially* result in a call to an * emergency service, for a specific country. * * Specifically, this method will return true if the specified number * is an emergency number in the specified country, *or* if the number * simply starts with the same digits as any emergency number for that * country. * * This method is intended for internal use by the phone app when * deciding whether to allow ACTION_CALL intents from 3rd party apps * (where we're required to *not* allow emergency calls to be placed.) * * @param number the number to look up. * @param defaultCountryIso the specific country which the number should be checked against * @return true if the number is an emergency number for the specific * country, *or* if the number starts with the same digits as * any of those emergency numbers. * * @hide */
public static boolean isPotentialEmergencyNumber(String number, String defaultCountryIso) { return isPotentialEmergencyNumber(getDefaultVoiceSubId(), number, defaultCountryIso); }
Checks if a given number might *potentially* result in a call to an emergency service, for a specific country. Specifically, this method will return true if the specified number is an emergency number in the specified country, *or* if the number simply starts with the same digits as any emergency number for that country. This method is intended for internal use by the phone app when deciding whether to allow ACTION_CALL intents from 3rd party apps (where we're required to *not* allow emergency calls to be placed.)
Params:
  • subId – the subscription id of the SIM.
  • number – the number to look up.
  • defaultCountryIso – the specific country which the number should be checked against
Returns:true if the number is an emergency number for the specific country, *or* if the number starts with the same digits as any of those emergency numbers.
@hide
/** * Checks if a given number might *potentially* result in a call to an * emergency service, for a specific country. * * Specifically, this method will return true if the specified number * is an emergency number in the specified country, *or* if the number * simply starts with the same digits as any emergency number for that * country. * * This method is intended for internal use by the phone app when * deciding whether to allow ACTION_CALL intents from 3rd party apps * (where we're required to *not* allow emergency calls to be placed.) * * @param subId the subscription id of the SIM. * @param number the number to look up. * @param defaultCountryIso the specific country which the number should be checked against * @return true if the number is an emergency number for the specific * country, *or* if the number starts with the same digits as * any of those emergency numbers. * @hide */
public static boolean isPotentialEmergencyNumber(int subId, String number, String defaultCountryIso) { return isEmergencyNumberInternal(subId, number, defaultCountryIso, false /* useExactMatch */); }
Helper function for isEmergencyNumber(String, String) and isPotentialEmergencyNumber(String, String).
Params:
  • number – the number to look up.
  • defaultCountryIso – the specific country which the number should be checked against
  • useExactMatch – if true, consider a number to be an emergency number only if it *exactly* matches a number listed in the RIL / SIM. If false, a number is considered to be an emergency number if it simply starts with the same digits as any of the emergency numbers listed in the RIL / SIM.
Returns:true if the number is an emergency number for the specified country.
/** * Helper function for isEmergencyNumber(String, String) and * isPotentialEmergencyNumber(String, String). * * @param number the number to look up. * @param defaultCountryIso the specific country which the number should be checked against * @param useExactMatch if true, consider a number to be an emergency * number only if it *exactly* matches a number listed in * the RIL / SIM. If false, a number is considered to be an * emergency number if it simply starts with the same digits * as any of the emergency numbers listed in the RIL / SIM. * * @return true if the number is an emergency number for the specified country. */
private static boolean isEmergencyNumberInternal(String number, String defaultCountryIso, boolean useExactMatch) { return isEmergencyNumberInternal(getDefaultVoiceSubId(), number, defaultCountryIso, useExactMatch); }
Helper function for isEmergencyNumber(String, String) and isPotentialEmergencyNumber(String, String).
Params:
  • subId – the subscription id of the SIM.
  • number – the number to look up.
  • defaultCountryIso – the specific country which the number should be checked against
  • useExactMatch – if true, consider a number to be an emergency number only if it *exactly* matches a number listed in the RIL / SIM. If false, a number is considered to be an emergency number if it simply starts with the same digits as any of the emergency numbers listed in the RIL / SIM.
Returns:true if the number is an emergency number for the specified country.
@hide
/** * Helper function for isEmergencyNumber(String, String) and * isPotentialEmergencyNumber(String, String). * * @param subId the subscription id of the SIM. * @param number the number to look up. * @param defaultCountryIso the specific country which the number should be checked against * @param useExactMatch if true, consider a number to be an emergency * number only if it *exactly* matches a number listed in * the RIL / SIM. If false, a number is considered to be an * emergency number if it simply starts with the same digits * as any of the emergency numbers listed in the RIL / SIM. * * @return true if the number is an emergency number for the specified country. * @hide */
private static boolean isEmergencyNumberInternal(int subId, String number, String defaultCountryIso, boolean useExactMatch) { // If the number passed in is null, just return false: if (number == null) return false; // If the number passed in is a SIP address, return false, since the // concept of "emergency numbers" is only meaningful for calls placed // over the cell network. // (Be sure to do this check *before* calling extractNetworkPortionAlt(), // since the whole point of extractNetworkPortionAlt() is to filter out // any non-dialable characters (which would turn 'abc911def@example.com' // into '911', for example.)) if (isUriNumber(number)) { return false; } // Strip the separators from the number before comparing it // to the list. number = extractNetworkPortionAlt(number); String emergencyNumbers = ""; int slotId = SubscriptionManager.getSlotIndex(subId); // retrieve the list of emergency numbers // check read-write ecclist property first String ecclist = (slotId <= 0) ? "ril.ecclist" : ("ril.ecclist" + slotId); emergencyNumbers = SystemProperties.get(ecclist, ""); Rlog.d(LOG_TAG, "slotId:" + slotId + " subId:" + subId + " country:" + defaultCountryIso + " emergencyNumbers: " + emergencyNumbers); if (TextUtils.isEmpty(emergencyNumbers)) { // then read-only ecclist property since old RIL only uses this emergencyNumbers = SystemProperties.get("ro.ril.ecclist"); } if (!TextUtils.isEmpty(emergencyNumbers)) { // searches through the comma-separated list for a match, // return true if one is found. for (String emergencyNum : emergencyNumbers.split(",")) { // It is not possible to append additional digits to an emergency number to dial // the number in Brazil - it won't connect. if (useExactMatch || "BR".equalsIgnoreCase(defaultCountryIso)) { if (number.equals(emergencyNum)) { return true; } } else { if (number.startsWith(emergencyNum)) { return true; } } } // no matches found against the list! return false; } Rlog.d(LOG_TAG, "System property doesn't provide any emergency numbers." + " Use embedded logic for determining ones."); // If slot id is invalid, means that there is no sim card. // According spec 3GPP TS22.101, the following numbers should be // ECC numbers when SIM/USIM is not present. emergencyNumbers = ((slotId < 0) ? "112,911,000,08,110,118,119,999" : "112,911"); for (String emergencyNum : emergencyNumbers.split(",")) { if (useExactMatch) { if (number.equals(emergencyNum)) { return true; } } else { if (number.startsWith(emergencyNum)) { return true; } } } // No ecclist system property, so use our own list. if (defaultCountryIso != null) { ShortNumberInfo info = ShortNumberInfo.getInstance(); if (useExactMatch) { return info.isEmergencyNumber(number, defaultCountryIso); } else { return info.connectsToEmergencyNumber(number, defaultCountryIso); } } return false; }
Checks if a given number is an emergency number for the country that the user is in.
Params:
  • number – the number to look up.
  • context – the specific context which the number should be checked against
Returns:true if the specified number is an emergency number for the country the user is currently in.
/** * Checks if a given number is an emergency number for the country that the user is in. * * @param number the number to look up. * @param context the specific context which the number should be checked against * @return true if the specified number is an emergency number for the country the user * is currently in. */
public static boolean isLocalEmergencyNumber(Context context, String number) { return isLocalEmergencyNumber(context, getDefaultVoiceSubId(), number); }
Checks if a given number is an emergency number for the country that the user is in.
Params:
  • subId – the subscription id of the SIM.
  • number – the number to look up.
  • context – the specific context which the number should be checked against
Returns:true if the specified number is an emergency number for the country the user is currently in.
@hide
/** * Checks if a given number is an emergency number for the country that the user is in. * * @param subId the subscription id of the SIM. * @param number the number to look up. * @param context the specific context which the number should be checked against * @return true if the specified number is an emergency number for the country the user * is currently in. * @hide */
public static boolean isLocalEmergencyNumber(Context context, int subId, String number) { return isLocalEmergencyNumberInternal(subId, number, context, true /* useExactMatch */); }
Checks if a given number might *potentially* result in a call to an emergency service, for the country that the user is in. The current country is determined using the CountryDetector. Specifically, this method will return true if the specified number is an emergency number in the current country, *or* if the number simply starts with the same digits as any emergency number for the current country. This method is intended for internal use by the phone app when deciding whether to allow ACTION_CALL intents from 3rd party apps (where we're required to *not* allow emergency calls to be placed.)
Params:
  • number – the number to look up.
  • context – the specific context which the number should be checked against
See Also:
Returns:true if the specified number is an emergency number for a local country, based on the CountryDetector.
@hide
/** * Checks if a given number might *potentially* result in a call to an * emergency service, for the country that the user is in. The current * country is determined using the CountryDetector. * * Specifically, this method will return true if the specified number * is an emergency number in the current country, *or* if the number * simply starts with the same digits as any emergency number for the * current country. * * This method is intended for internal use by the phone app when * deciding whether to allow ACTION_CALL intents from 3rd party apps * (where we're required to *not* allow emergency calls to be placed.) * * @param number the number to look up. * @param context the specific context which the number should be checked against * @return true if the specified number is an emergency number for a local country, based on the * CountryDetector. * * @see android.location.CountryDetector * @hide */
public static boolean isPotentialLocalEmergencyNumber(Context context, String number) { return isPotentialLocalEmergencyNumber(context, getDefaultVoiceSubId(), number); }
Checks if a given number might *potentially* result in a call to an emergency service, for the country that the user is in. The current country is determined using the CountryDetector. Specifically, this method will return true if the specified number is an emergency number in the current country, *or* if the number simply starts with the same digits as any emergency number for the current country. This method is intended for internal use by the phone app when deciding whether to allow ACTION_CALL intents from 3rd party apps (where we're required to *not* allow emergency calls to be placed.)
Params:
  • subId – the subscription id of the SIM.
  • number – the number to look up.
  • context – the specific context which the number should be checked against
Returns:true if the specified number is an emergency number for a local country, based on the CountryDetector.
@hide
/** * Checks if a given number might *potentially* result in a call to an * emergency service, for the country that the user is in. The current * country is determined using the CountryDetector. * * Specifically, this method will return true if the specified number * is an emergency number in the current country, *or* if the number * simply starts with the same digits as any emergency number for the * current country. * * This method is intended for internal use by the phone app when * deciding whether to allow ACTION_CALL intents from 3rd party apps * (where we're required to *not* allow emergency calls to be placed.) * * @param subId the subscription id of the SIM. * @param number the number to look up. * @param context the specific context which the number should be checked against * @return true if the specified number is an emergency number for a local country, based on the * CountryDetector. * * @hide */
public static boolean isPotentialLocalEmergencyNumber(Context context, int subId, String number) { return isLocalEmergencyNumberInternal(subId, number, context, false /* useExactMatch */); }
Helper function for isLocalEmergencyNumber() and isPotentialLocalEmergencyNumber().
Params:
  • number – the number to look up.
  • context – the specific context which the number should be checked against
  • useExactMatch – if true, consider a number to be an emergency number only if it *exactly* matches a number listed in the RIL / SIM. If false, a number is considered to be an emergency number if it simply starts with the same digits as any of the emergency numbers listed in the RIL / SIM.
See Also:
Returns:true if the specified number is an emergency number for a local country, based on the CountryDetector.
@hide
/** * Helper function for isLocalEmergencyNumber() and * isPotentialLocalEmergencyNumber(). * * @param number the number to look up. * @param context the specific context which the number should be checked against * @param useExactMatch if true, consider a number to be an emergency * number only if it *exactly* matches a number listed in * the RIL / SIM. If false, a number is considered to be an * emergency number if it simply starts with the same digits * as any of the emergency numbers listed in the RIL / SIM. * * @return true if the specified number is an emergency number for a * local country, based on the CountryDetector. * * @see android.location.CountryDetector * @hide */
private static boolean isLocalEmergencyNumberInternal(String number, Context context, boolean useExactMatch) { return isLocalEmergencyNumberInternal(getDefaultVoiceSubId(), number, context, useExactMatch); }
Helper function for isLocalEmergencyNumber() and isPotentialLocalEmergencyNumber().
Params:
  • subId – the subscription id of the SIM.
  • number – the number to look up.
  • context – the specific context which the number should be checked against
  • useExactMatch – if true, consider a number to be an emergency number only if it *exactly* matches a number listed in the RIL / SIM. If false, a number is considered to be an emergency number if it simply starts with the same digits as any of the emergency numbers listed in the RIL / SIM.
Returns:true if the specified number is an emergency number for a local country, based on the CountryDetector.
@hide
/** * Helper function for isLocalEmergencyNumber() and * isPotentialLocalEmergencyNumber(). * * @param subId the subscription id of the SIM. * @param number the number to look up. * @param context the specific context which the number should be checked against * @param useExactMatch if true, consider a number to be an emergency * number only if it *exactly* matches a number listed in * the RIL / SIM. If false, a number is considered to be an * emergency number if it simply starts with the same digits * as any of the emergency numbers listed in the RIL / SIM. * * @return true if the specified number is an emergency number for a * local country, based on the CountryDetector. * @hide */
private static boolean isLocalEmergencyNumberInternal(int subId, String number, Context context, boolean useExactMatch) { String countryIso; CountryDetector detector = (CountryDetector) context.getSystemService( Context.COUNTRY_DETECTOR); if (detector != null && detector.detectCountry() != null) { countryIso = detector.detectCountry().getCountryIso(); } else { Locale locale = context.getResources().getConfiguration().locale; countryIso = locale.getCountry(); Rlog.w(LOG_TAG, "No CountryDetector; falling back to countryIso based on locale: " + countryIso); } return isEmergencyNumberInternal(subId, number, countryIso, useExactMatch); }
isVoiceMailNumber: checks a given number against the voicemail number provided by the RIL and SIM card. The caller must have the READ_PHONE_STATE credential.
Params:
  • number – the number to look up.
Returns:true if the number is in the list of voicemail. False otherwise, including if the caller does not have the permission to read the VM number.
/** * isVoiceMailNumber: checks a given number against the voicemail * number provided by the RIL and SIM card. The caller must have * the READ_PHONE_STATE credential. * * @param number the number to look up. * @return true if the number is in the list of voicemail. False * otherwise, including if the caller does not have the permission * to read the VM number. */
public static boolean isVoiceMailNumber(String number) { return isVoiceMailNumber(SubscriptionManager.getDefaultSubscriptionId(), number); }
isVoiceMailNumber: checks a given number against the voicemail number provided by the RIL and SIM card. The caller must have the READ_PHONE_STATE credential.
Params:
  • subId – the subscription id of the SIM.
  • number – the number to look up.
Returns:true if the number is in the list of voicemail. False otherwise, including if the caller does not have the permission to read the VM number.
@hide
/** * isVoiceMailNumber: checks a given number against the voicemail * number provided by the RIL and SIM card. The caller must have * the READ_PHONE_STATE credential. * * @param subId the subscription id of the SIM. * @param number the number to look up. * @return true if the number is in the list of voicemail. False * otherwise, including if the caller does not have the permission * to read the VM number. * @hide */
public static boolean isVoiceMailNumber(int subId, String number) { return isVoiceMailNumber(null, subId, number); }
isVoiceMailNumber: checks a given number against the voicemail number provided by the RIL and SIM card. The caller must have the READ_PHONE_STATE credential.
Params:
  • context – Context.
  • subId – the subscription id of the SIM.
  • number – the number to look up.
Returns:true if the number is in the list of voicemail. False otherwise, including if the caller does not have the permission to read the VM number.
@hide
/** * isVoiceMailNumber: checks a given number against the voicemail * number provided by the RIL and SIM card. The caller must have * the READ_PHONE_STATE credential. * * @param context {@link Context}. * @param subId the subscription id of the SIM. * @param number the number to look up. * @return true if the number is in the list of voicemail. False * otherwise, including if the caller does not have the permission * to read the VM number. * @hide */
public static boolean isVoiceMailNumber(Context context, int subId, String number) { String vmNumber, mdn; try { final TelephonyManager tm; if (context == null) { tm = TelephonyManager.getDefault(); if (DBG) log("isVoiceMailNumber: default tm"); } else { tm = TelephonyManager.from(context); if (DBG) log("isVoiceMailNumber: tm from context"); } vmNumber = tm.getVoiceMailNumber(subId); mdn = tm.getLine1Number(subId); if (DBG) log("isVoiceMailNumber: mdn=" + mdn + ", vmNumber=" + vmNumber + ", number=" + number); } catch (SecurityException ex) { if (DBG) log("isVoiceMailNumber: SecurityExcpetion caught"); return false; } // Strip the separators from the number before comparing it // to the list. number = extractNetworkPortionAlt(number); if (TextUtils.isEmpty(number)) { if (DBG) log("isVoiceMailNumber: number is empty after stripping"); return false; } // check if the carrier considers MDN to be an additional voicemail number boolean compareWithMdn = false; if (context != null) { CarrierConfigManager configManager = (CarrierConfigManager) context.getSystemService(Context.CARRIER_CONFIG_SERVICE); if (configManager != null) { PersistableBundle b = configManager.getConfigForSubId(subId); if (b != null) { compareWithMdn = b.getBoolean(CarrierConfigManager. KEY_MDN_IS_ADDITIONAL_VOICEMAIL_NUMBER_BOOL); if (DBG) log("isVoiceMailNumber: compareWithMdn=" + compareWithMdn); } } } if (compareWithMdn) { if (DBG) log("isVoiceMailNumber: treating mdn as additional vm number"); return compare(number, vmNumber) || compare(number, mdn); } else { if (DBG) log("isVoiceMailNumber: returning regular compare"); return compare(number, vmNumber); } }
Translates any alphabetic letters (i.e. [A-Za-z]) in the specified phone number into the equivalent numeric digits, according to the phone keypad letter mapping described in ITU E.161 and ISO/IEC 9995-8.
Returns:the input string, with alpha letters converted to numeric digits using the phone keypad letter mapping. For example, an input of "1-800-GOOG-411" will return "1-800-4664-411".
/** * Translates any alphabetic letters (i.e. [A-Za-z]) in the * specified phone number into the equivalent numeric digits, * according to the phone keypad letter mapping described in * ITU E.161 and ISO/IEC 9995-8. * * @return the input string, with alpha letters converted to numeric * digits using the phone keypad letter mapping. For example, * an input of "1-800-GOOG-411" will return "1-800-4664-411". */
public static String convertKeypadLettersToDigits(String input) { if (input == null) { return input; } int len = input.length(); if (len == 0) { return input; } char[] out = input.toCharArray(); for (int i = 0; i < len; i++) { char c = out[i]; // If this char isn't in KEYPAD_MAP at all, just leave it alone. out[i] = (char) KEYPAD_MAP.get(c, c); } return new String(out); }
The phone keypad letter mapping (see ITU E.161 or ISO/IEC 9995-8.) TODO: This should come from a resource.
/** * The phone keypad letter mapping (see ITU E.161 or ISO/IEC 9995-8.) * TODO: This should come from a resource. */
private static final SparseIntArray KEYPAD_MAP = new SparseIntArray(); static { KEYPAD_MAP.put('a', '2'); KEYPAD_MAP.put('b', '2'); KEYPAD_MAP.put('c', '2'); KEYPAD_MAP.put('A', '2'); KEYPAD_MAP.put('B', '2'); KEYPAD_MAP.put('C', '2'); KEYPAD_MAP.put('d', '3'); KEYPAD_MAP.put('e', '3'); KEYPAD_MAP.put('f', '3'); KEYPAD_MAP.put('D', '3'); KEYPAD_MAP.put('E', '3'); KEYPAD_MAP.put('F', '3'); KEYPAD_MAP.put('g', '4'); KEYPAD_MAP.put('h', '4'); KEYPAD_MAP.put('i', '4'); KEYPAD_MAP.put('G', '4'); KEYPAD_MAP.put('H', '4'); KEYPAD_MAP.put('I', '4'); KEYPAD_MAP.put('j', '5'); KEYPAD_MAP.put('k', '5'); KEYPAD_MAP.put('l', '5'); KEYPAD_MAP.put('J', '5'); KEYPAD_MAP.put('K', '5'); KEYPAD_MAP.put('L', '5'); KEYPAD_MAP.put('m', '6'); KEYPAD_MAP.put('n', '6'); KEYPAD_MAP.put('o', '6'); KEYPAD_MAP.put('M', '6'); KEYPAD_MAP.put('N', '6'); KEYPAD_MAP.put('O', '6'); KEYPAD_MAP.put('p', '7'); KEYPAD_MAP.put('q', '7'); KEYPAD_MAP.put('r', '7'); KEYPAD_MAP.put('s', '7'); KEYPAD_MAP.put('P', '7'); KEYPAD_MAP.put('Q', '7'); KEYPAD_MAP.put('R', '7'); KEYPAD_MAP.put('S', '7'); KEYPAD_MAP.put('t', '8'); KEYPAD_MAP.put('u', '8'); KEYPAD_MAP.put('v', '8'); KEYPAD_MAP.put('T', '8'); KEYPAD_MAP.put('U', '8'); KEYPAD_MAP.put('V', '8'); KEYPAD_MAP.put('w', '9'); KEYPAD_MAP.put('x', '9'); KEYPAD_MAP.put('y', '9'); KEYPAD_MAP.put('z', '9'); KEYPAD_MAP.put('W', '9'); KEYPAD_MAP.put('X', '9'); KEYPAD_MAP.put('Y', '9'); KEYPAD_MAP.put('Z', '9'); } //================ Plus Code formatting ========================= private static final char PLUS_SIGN_CHAR = '+'; private static final String PLUS_SIGN_STRING = "+"; private static final String NANP_IDP_STRING = "011"; private static final int NANP_LENGTH = 10;
This function checks if there is a plus sign (+) in the passed-in dialing number. If there is, it processes the plus sign based on the default telephone numbering plan of the system when the phone is activated and the current telephone numbering plan of the system that the phone is camped on. Currently, we only support the case that the default and current telephone numbering plans are North American Numbering Plan(NANP). The passed-in dialStr should only contain the valid format as described below, 1) the 1st character in the dialStr should be one of the really dialable characters listed below ISO-LATIN characters 0-9, *, # , + 2) the dialStr should already strip out the separator characters, every character in the dialStr should be one of the non separator characters listed below ISO-LATIN characters 0-9, *, # , +, WILD, WAIT, PAUSE Otherwise, this function returns the dial string passed in
Params:
  • dialStr – the original dial string
Returns:the converted dial string if the current/default countries belong to NANP, and if there is the "+" in the original dial string. Otherwise, the original dial string returns. This API is for CDMA only
@hideTODO: pending API Council approval
/** * This function checks if there is a plus sign (+) in the passed-in dialing number. * If there is, it processes the plus sign based on the default telephone * numbering plan of the system when the phone is activated and the current * telephone numbering plan of the system that the phone is camped on. * Currently, we only support the case that the default and current telephone * numbering plans are North American Numbering Plan(NANP). * * The passed-in dialStr should only contain the valid format as described below, * 1) the 1st character in the dialStr should be one of the really dialable * characters listed below * ISO-LATIN characters 0-9, *, # , + * 2) the dialStr should already strip out the separator characters, * every character in the dialStr should be one of the non separator characters * listed below * ISO-LATIN characters 0-9, *, # , +, WILD, WAIT, PAUSE * * Otherwise, this function returns the dial string passed in * * @param dialStr the original dial string * @return the converted dial string if the current/default countries belong to NANP, * and if there is the "+" in the original dial string. Otherwise, the original dial * string returns. * * This API is for CDMA only * * @hide TODO: pending API Council approval */
public static String cdmaCheckAndProcessPlusCode(String dialStr) { if (!TextUtils.isEmpty(dialStr)) { if (isReallyDialable(dialStr.charAt(0)) && isNonSeparator(dialStr)) { String currIso = TelephonyManager.getDefault().getNetworkCountryIso(); String defaultIso = TelephonyManager.getDefault().getSimCountryIso(); if (!TextUtils.isEmpty(currIso) && !TextUtils.isEmpty(defaultIso)) { return cdmaCheckAndProcessPlusCodeByNumberFormat(dialStr, getFormatTypeFromCountryCode(currIso), getFormatTypeFromCountryCode(defaultIso)); } } } return dialStr; }
Process phone number for CDMA, converting plus code using the home network number format. This is used for outgoing SMS messages.
Params:
  • dialStr – the original dial string
Returns:the converted dial string
@hidefor internal use
/** * Process phone number for CDMA, converting plus code using the home network number format. * This is used for outgoing SMS messages. * * @param dialStr the original dial string * @return the converted dial string * @hide for internal use */
public static String cdmaCheckAndProcessPlusCodeForSms(String dialStr) { if (!TextUtils.isEmpty(dialStr)) { if (isReallyDialable(dialStr.charAt(0)) && isNonSeparator(dialStr)) { String defaultIso = TelephonyManager.getDefault().getSimCountryIso(); if (!TextUtils.isEmpty(defaultIso)) { int format = getFormatTypeFromCountryCode(defaultIso); return cdmaCheckAndProcessPlusCodeByNumberFormat(dialStr, format, format); } } } return dialStr; }
This function should be called from checkAndProcessPlusCode only And it is used for test purpose also. It checks the dial string by looping through the network portion, post dial portion 1, post dial porting 2, etc. If there is any plus sign, then process the plus sign. Currently, this function supports the plus sign conversion within NANP only. Specifically, it handles the plus sign in the following ways: 1)+1NANP,remove +, e.g. +18475797000 is converted to 18475797000, 2)+NANP or +non-NANP Numbers,replace + with the current NANP IDP, e.g, +8475797000 is converted to 0118475797000, +11875767800 is converted to 01111875767800 3)+1NANP in post dial string(s), e.g. 8475797000;+18475231753 is converted to 8475797000;18475231753
Params:
  • dialStr – the original dial string
  • currFormat – the numbering system of the current country that the phone is camped on
  • defaultFormat – the numbering system of the country that the phone is activated on
Returns:the converted dial string if the current/default countries belong to NANP, and if there is the "+" in the original dial string. Otherwise, the original dial string returns.
@hide
/** * This function should be called from checkAndProcessPlusCode only * And it is used for test purpose also. * * It checks the dial string by looping through the network portion, * post dial portion 1, post dial porting 2, etc. If there is any * plus sign, then process the plus sign. * Currently, this function supports the plus sign conversion within NANP only. * Specifically, it handles the plus sign in the following ways: * 1)+1NANP,remove +, e.g. * +18475797000 is converted to 18475797000, * 2)+NANP or +non-NANP Numbers,replace + with the current NANP IDP, e.g, * +8475797000 is converted to 0118475797000, * +11875767800 is converted to 01111875767800 * 3)+1NANP in post dial string(s), e.g. * 8475797000;+18475231753 is converted to 8475797000;18475231753 * * * @param dialStr the original dial string * @param currFormat the numbering system of the current country that the phone is camped on * @param defaultFormat the numbering system of the country that the phone is activated on * @return the converted dial string if the current/default countries belong to NANP, * and if there is the "+" in the original dial string. Otherwise, the original dial * string returns. * * @hide */
public static String cdmaCheckAndProcessPlusCodeByNumberFormat(String dialStr,int currFormat,int defaultFormat) { String retStr = dialStr; boolean useNanp = (currFormat == defaultFormat) && (currFormat == FORMAT_NANP); // Checks if the plus sign character is in the passed-in dial string if (dialStr != null && dialStr.lastIndexOf(PLUS_SIGN_STRING) != -1) { // Handle case where default and current telephone numbering plans are NANP. String postDialStr = null; String tempDialStr = dialStr; // Sets the retStr to null since the conversion will be performed below. retStr = null; if (DBG) log("checkAndProcessPlusCode,dialStr=" + dialStr); // This routine is to process the plus sign in the dial string by loop through // the network portion, post dial portion 1, post dial portion 2... etc. if // applied do { String networkDialStr; // Format the string based on the rules for the country the number is from, // and the current country the phone is camped if (useNanp) { networkDialStr = extractNetworkPortion(tempDialStr); } else { networkDialStr = extractNetworkPortionAlt(tempDialStr); } networkDialStr = processPlusCode(networkDialStr, useNanp); // Concatenates the string that is converted from network portion if (!TextUtils.isEmpty(networkDialStr)) { if (retStr == null) { retStr = networkDialStr; } else { retStr = retStr.concat(networkDialStr); } } else { // This should never happen since we checked the if dialStr is null // and if it contains the plus sign in the beginning of this function. // The plus sign is part of the network portion. Rlog.e("checkAndProcessPlusCode: null newDialStr", networkDialStr); return dialStr; } postDialStr = extractPostDialPortion(tempDialStr); if (!TextUtils.isEmpty(postDialStr)) { int dialableIndex = findDialableIndexFromPostDialStr(postDialStr); // dialableIndex should always be greater than 0 if (dialableIndex >= 1) { retStr = appendPwCharBackToOrigDialStr(dialableIndex, retStr,postDialStr); // Skips the P/W character, extracts the dialable portion tempDialStr = postDialStr.substring(dialableIndex); } else { // Non-dialable character such as P/W should not be at the end of // the dial string after P/W processing in GsmCdmaConnection.java // Set the postDialStr to "" to break out of the loop if (dialableIndex < 0) { postDialStr = ""; } Rlog.e("wrong postDialStr=", postDialStr); } } if (DBG) log("checkAndProcessPlusCode,postDialStr=" + postDialStr); } while (!TextUtils.isEmpty(postDialStr) && !TextUtils.isEmpty(tempDialStr)); } return retStr; }
Wrap the supplied CharSequence with a TtsSpan, annotating it as containing a phone number in its entirety.
Params:
  • phoneNumber – A CharSequence the entirety of which represents a phone number.
Returns:A CharSequence with appropriate annotations.
/** * Wrap the supplied {@code CharSequence} with a {@code TtsSpan}, annotating it as * containing a phone number in its entirety. * * @param phoneNumber A {@code CharSequence} the entirety of which represents a phone number. * @return A {@code CharSequence} with appropriate annotations. */
public static CharSequence createTtsSpannable(CharSequence phoneNumber) { if (phoneNumber == null) { return null; } Spannable spannable = Spannable.Factory.getInstance().newSpannable(phoneNumber); PhoneNumberUtils.addTtsSpan(spannable, 0, spannable.length()); return spannable; }
Attach a TtsSpan to the supplied Spannable at the indicated location, annotating that location as containing a phone number.
Params:
  • s – A Spannable to annotate.
  • start – The starting character position of the phone number in s.
  • endExclusive – The position after the ending character in the phone number s.
/** * Attach a {@link TtsSpan} to the supplied {@code Spannable} at the indicated location, * annotating that location as containing a phone number. * * @param s A {@code Spannable} to annotate. * @param start The starting character position of the phone number in {@code s}. * @param endExclusive The position after the ending character in the phone number {@code s}. */
public static void addTtsSpan(Spannable s, int start, int endExclusive) { s.setSpan(createTtsSpan(s.subSequence(start, endExclusive).toString()), start, endExclusive, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); }
Wrap the supplied CharSequence with a TtsSpan, annotating it as containing a phone number in its entirety.
Params:
  • phoneNumber – A CharSequence the entirety of which represents a phone number.
Returns:A CharSequence with appropriate annotations.
Deprecated:Renamed createTtsSpannable.
@hide
/** * Wrap the supplied {@code CharSequence} with a {@code TtsSpan}, annotating it as * containing a phone number in its entirety. * * @param phoneNumber A {@code CharSequence} the entirety of which represents a phone number. * @return A {@code CharSequence} with appropriate annotations. * @deprecated Renamed {@link #createTtsSpannable}. * * @hide */
@Deprecated public static CharSequence ttsSpanAsPhoneNumber(CharSequence phoneNumber) { return createTtsSpannable(phoneNumber); }
Attach a TtsSpan to the supplied Spannable at the indicated location, annotating that location as containing a phone number.
Params:
  • s – A Spannable to annotate.
  • start – The starting character position of the phone number in s.
  • end – The ending character position of the phone number in s.
Deprecated:Renamed addTtsSpan.
@hide
/** * Attach a {@link TtsSpan} to the supplied {@code Spannable} at the indicated location, * annotating that location as containing a phone number. * * @param s A {@code Spannable} to annotate. * @param start The starting character position of the phone number in {@code s}. * @param end The ending character position of the phone number in {@code s}. * * @deprecated Renamed {@link #addTtsSpan}. * * @hide */
@Deprecated public static void ttsSpanAsPhoneNumber(Spannable s, int start, int end) { addTtsSpan(s, start, end); }
Create a TtsSpan for the supplied String.
Params:
  • phoneNumberString – A String the entirety of which represents a phone number.
Returns:A TtsSpan for {@param phoneNumberString}.
/** * Create a {@code TtsSpan} for the supplied {@code String}. * * @param phoneNumberString A {@code String} the entirety of which represents a phone number. * @return A {@code TtsSpan} for {@param phoneNumberString}. */
public static TtsSpan createTtsSpan(String phoneNumberString) { if (phoneNumberString == null) { return null; } // Parse the phone number final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance(); PhoneNumber phoneNumber = null; try { // Don't supply a defaultRegion so this fails for non-international numbers because // we don't want to TalkBalk to read a country code (e.g. +1) if it is not already // present phoneNumber = phoneNumberUtil.parse(phoneNumberString, /* defaultRegion */ null); } catch (NumberParseException ignored) { } // Build a telephone tts span final TtsSpan.TelephoneBuilder builder = new TtsSpan.TelephoneBuilder(); if (phoneNumber == null) { // Strip separators otherwise TalkBack will be silent // (this behavior was observed with TalkBalk 4.0.2 from their alpha channel) builder.setNumberParts(splitAtNonNumerics(phoneNumberString)); } else { if (phoneNumber.hasCountryCode()) { builder.setCountryCode(Integer.toString(phoneNumber.getCountryCode())); } builder.setNumberParts(Long.toString(phoneNumber.getNationalNumber())); } return builder.build(); } // Split a phone number like "+20(123)-456#" using spaces, ignoring anything that is not // a digit or the characters * and #, to produce a result like "20 123 456#". private static String splitAtNonNumerics(CharSequence number) { StringBuilder sb = new StringBuilder(number.length()); for (int i = 0; i < number.length(); i++) { sb.append(PhoneNumberUtils.is12Key(number.charAt(i)) ? number.charAt(i) : " "); } // It is very important to remove extra spaces. At time of writing, any leading or trailing // spaces, or any sequence of more than one space, will confuse TalkBack and cause the TTS // span to be non-functional! return sb.toString().replaceAll(" +", " ").trim(); } private static String getCurrentIdp(boolean useNanp) { String ps = null; if (useNanp) { ps = NANP_IDP_STRING; } else { // in case, there is no IDD is found, we shouldn't convert it. ps = SystemProperties.get(PROPERTY_OPERATOR_IDP_STRING, PLUS_SIGN_STRING); } return ps; } private static boolean isTwoToNine (char c) { if (c >= '2' && c <= '9') { return true; } else { return false; } } private static int getFormatTypeFromCountryCode (String country) { // Check for the NANP countries int length = NANP_COUNTRIES.length; for (int i = 0; i < length; i++) { if (NANP_COUNTRIES[i].compareToIgnoreCase(country) == 0) { return FORMAT_NANP; } } if ("jp".compareToIgnoreCase(country) == 0) { return FORMAT_JAPAN; } return FORMAT_UNKNOWN; }
This function checks if the passed in string conforms to the NANP format i.e. NXX-NXX-XXXX, N is any digit 2-9 and X is any digit 0-9
@hide
/** * This function checks if the passed in string conforms to the NANP format * i.e. NXX-NXX-XXXX, N is any digit 2-9 and X is any digit 0-9 * @hide */
public static boolean isNanp (String dialStr) { boolean retVal = false; if (dialStr != null) { if (dialStr.length() == NANP_LENGTH) { if (isTwoToNine(dialStr.charAt(0)) && isTwoToNine(dialStr.charAt(3))) { retVal = true; for (int i=1; i<NANP_LENGTH; i++ ) { char c=dialStr.charAt(i); if (!PhoneNumberUtils.isISODigit(c)) { retVal = false; break; } } } } } else { Rlog.e("isNanp: null dialStr passed in", dialStr); } return retVal; }
This function checks if the passed in string conforms to 1-NANP format
/** * This function checks if the passed in string conforms to 1-NANP format */
private static boolean isOneNanp(String dialStr) { boolean retVal = false; if (dialStr != null) { String newDialStr = dialStr.substring(1); if ((dialStr.charAt(0) == '1') && isNanp(newDialStr)) { retVal = true; } } else { Rlog.e("isOneNanp: null dialStr passed in", dialStr); } return retVal; }
Determines if the specified number is actually a URI (i.e. a SIP address) rather than a regular PSTN phone number, based on whether or not the number contains an "@" character.
Params:
  • number –
@hide
Returns:true if number contains @
/** * Determines if the specified number is actually a URI * (i.e. a SIP address) rather than a regular PSTN phone number, * based on whether or not the number contains an "@" character. * * @hide * @param number * @return true if number contains @ */
public static boolean isUriNumber(String number) { // Note we allow either "@" or "%40" to indicate a URI, in case // the passed-in string is URI-escaped. (Neither "@" nor "%40" // will ever be found in a legal PSTN number.) return number != null && (number.contains("@") || number.contains("%40")); }
Params:
  • number – SIP address of the form "username@domainname" (or the URI-escaped equivalent "username%40domainname")
See Also:
Returns:the "username" part of the specified SIP address, i.e. the part before the "@" character (or "%40").
@hide
/** * @return the "username" part of the specified SIP address, * i.e. the part before the "@" character (or "%40"). * * @param number SIP address of the form "username@domainname" * (or the URI-escaped equivalent "username%40domainname") * @see #isUriNumber * * @hide */
public static String getUsernameFromUriNumber(String number) { // The delimiter between username and domain name can be // either "@" or "%40" (the URI-escaped equivalent.) int delimiterIndex = number.indexOf('@'); if (delimiterIndex < 0) { delimiterIndex = number.indexOf("%40"); } if (delimiterIndex < 0) { Rlog.w(LOG_TAG, "getUsernameFromUriNumber: no delimiter found in SIP addr '" + number + "'"); delimiterIndex = number.length(); } return number.substring(0, delimiterIndex); }
Given a Uri with a sip scheme, attempts to build an equivalent tel scheme Uri. If the source Uri does not contain a valid number, or is not using the sip scheme, the original Uri is returned.
Params:
  • source – The Uri to convert.
Returns:The equivalent tel scheme Uri.
@hide
/** * Given a {@link Uri} with a {@code sip} scheme, attempts to build an equivalent {@code tel} * scheme {@link Uri}. If the source {@link Uri} does not contain a valid number, or is not * using the {@code sip} scheme, the original {@link Uri} is returned. * * @param source The {@link Uri} to convert. * @return The equivalent {@code tel} scheme {@link Uri}. * * @hide */
public static Uri convertSipUriToTelUri(Uri source) { // A valid SIP uri has the format: sip:user:password@host:port;uri-parameters?headers // Per RFC3261, the "user" can be a telephone number. // For example: sip:1650555121;phone-context=blah.com@host.com // In this case, the phone number is in the user field of the URI, and the parameters can be // ignored. // // A SIP URI can also specify a phone number in a format similar to: // sip:+1-212-555-1212@something.com;user=phone // In this case, the phone number is again in user field and the parameters can be ignored. // We can get the user field in these instances by splitting the string on the @, ;, or : // and looking at the first found item. String scheme = source.getScheme(); if (!PhoneAccount.SCHEME_SIP.equals(scheme)) { // Not a sip URI, bail. return source; } String number = source.getSchemeSpecificPart(); String numberParts[] = number.split("[@;:]"); if (numberParts.length == 0) { // Number not found, bail. return source; } number = numberParts[0]; return Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null); }
This function handles the plus code conversion If the number format is 1)+1NANP,remove +, 2)other than +1NANP, any + numbers,replace + with the current IDP
/** * This function handles the plus code conversion * If the number format is * 1)+1NANP,remove +, * 2)other than +1NANP, any + numbers,replace + with the current IDP */
private static String processPlusCode(String networkDialStr, boolean useNanp) { String retStr = networkDialStr; if (DBG) log("processPlusCode, networkDialStr = " + networkDialStr + "for NANP = " + useNanp); // If there is a plus sign at the beginning of the dial string, // Convert the plus sign to the default IDP since it's an international number if (networkDialStr != null && networkDialStr.charAt(0) == PLUS_SIGN_CHAR && networkDialStr.length() > 1) { String newStr = networkDialStr.substring(1); // TODO: for nonNanp, should the '+' be removed if following number is country code if (useNanp && isOneNanp(newStr)) { // Remove the leading plus sign retStr = newStr; } else { // Replaces the plus sign with the default IDP retStr = networkDialStr.replaceFirst("[+]", getCurrentIdp(useNanp)); } } if (DBG) log("processPlusCode, retStr=" + retStr); return retStr; } // This function finds the index of the dialable character(s) // in the post dial string private static int findDialableIndexFromPostDialStr(String postDialStr) { for (int index = 0;index < postDialStr.length();index++) { char c = postDialStr.charAt(index); if (isReallyDialable(c)) { return index; } } return -1; } // This function appends the non-dialable P/W character to the original // dial string based on the dialable index passed in private static String appendPwCharBackToOrigDialStr(int dialableIndex,String origStr, String dialStr) { String retStr; // There is only 1 P/W character before the dialable characters if (dialableIndex == 1) { StringBuilder ret = new StringBuilder(origStr); ret = ret.append(dialStr.charAt(0)); retStr = ret.toString(); } else { // It means more than 1 P/W characters in the post dial string, // appends to retStr String nonDigitStr = dialStr.substring(0,dialableIndex); retStr = origStr.concat(nonDigitStr); } return retStr; } //===== Beginning of utility methods used in compareLoosely() ===== /** * Phone numbers are stored in "lookup" form in the database * as reversed strings to allow for caller ID lookup * * This method takes a phone number and makes a valid SQL "LIKE" * string that will match the lookup form * */
all of a up to len must be an international prefix or separators/non-dialing digits
/** all of a up to len must be an international prefix or * separators/non-dialing digits */
private static boolean matchIntlPrefix(String a, int len) { /* '([^0-9*#+pwn]\+[^0-9*#+pwn] | [^0-9*#+pwn]0(0|11)[^0-9*#+pwn] )$' */ /* 0 1 2 3 45 */ int state = 0; for (int i = 0 ; i < len ; i++) { char c = a.charAt(i); switch (state) { case 0: if (c == '+') state = 1; else if (c == '0') state = 2; else if (isNonSeparator(c)) return false; break; case 2: if (c == '0') state = 3; else if (c == '1') state = 4; else if (isNonSeparator(c)) return false; break; case 4: if (c == '1') state = 5; else if (isNonSeparator(c)) return false; break; default: if (isNonSeparator(c)) return false; break; } } return state == 1 || state == 3 || state == 5; }
all of 'a' up to len must be a (+|00|011)country code) We're fast and loose with the country code. Any \d{1,3} matches
/** all of 'a' up to len must be a (+|00|011)country code) * We're fast and loose with the country code. Any \d{1,3} matches */
private static boolean matchIntlPrefixAndCC(String a, int len) { /* [^0-9*#+pwn]*(\+|0(0|11)\d\d?\d? [^0-9*#+pwn] $ */ /* 0 1 2 3 45 6 7 8 */ int state = 0; for (int i = 0 ; i < len ; i++ ) { char c = a.charAt(i); switch (state) { case 0: if (c == '+') state = 1; else if (c == '0') state = 2; else if (isNonSeparator(c)) return false; break; case 2: if (c == '0') state = 3; else if (c == '1') state = 4; else if (isNonSeparator(c)) return false; break; case 4: if (c == '1') state = 5; else if (isNonSeparator(c)) return false; break; case 1: case 3: case 5: if (isISODigit(c)) state = 6; else if (isNonSeparator(c)) return false; break; case 6: case 7: if (isISODigit(c)) state++; else if (isNonSeparator(c)) return false; break; default: if (isNonSeparator(c)) return false; } } return state == 6 || state == 7 || state == 8; }
all of 'a' up to len must match non-US trunk prefix ('0')
/** all of 'a' up to len must match non-US trunk prefix ('0') */
private static boolean matchTrunkPrefix(String a, int len) { boolean found; found = false; for (int i = 0 ; i < len ; i++) { char c = a.charAt(i); if (c == '0' && !found) { found = true; } else if (isNonSeparator(c)) { return false; } } return found; } //===== End of utility methods used only in compareLoosely() ===== //===== Beginning of utility methods used only in compareStrictly() ==== /* * If true, the number is country calling code. */ private static final boolean COUNTRY_CALLING_CALL[] = { true, true, false, false, false, false, false, true, false, false, false, false, false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, true, true, false, true, true, true, true, true, false, true, false, false, true, true, false, false, true, true, true, true, true, true, true, false, true, true, true, true, true, true, true, true, false, true, true, true, true, true, true, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, true, true, true, false, true, false, false, true, true, true, true, true, true, true, false, false, true, false, }; private static final int CCC_LENGTH = COUNTRY_CALLING_CALL.length;
Returns:true when input is valid Country Calling Code.
/** * @return true when input is valid Country Calling Code. */
private static boolean isCountryCallingCode(int countryCallingCodeCandidate) { return countryCallingCodeCandidate > 0 && countryCallingCodeCandidate < CCC_LENGTH && COUNTRY_CALLING_CALL[countryCallingCodeCandidate]; }
Returns integer corresponding to the input if input "ch" is ISO-LATIN characters 0-9. Returns -1 otherwise
/** * Returns integer corresponding to the input if input "ch" is * ISO-LATIN characters 0-9. * Returns -1 otherwise */
private static int tryGetISODigit(char ch) { if ('0' <= ch && ch <= '9') { return ch - '0'; } else { return -1; } } private static class CountryCallingCodeAndNewIndex { public final int countryCallingCode; public final int newIndex; public CountryCallingCodeAndNewIndex(int countryCode, int newIndex) { this.countryCallingCode = countryCode; this.newIndex = newIndex; } } /* * Note that this function does not strictly care the country calling code with * 3 length (like Morocco: +212), assuming it is enough to use the first two * digit to compare two phone numbers. */ private static CountryCallingCodeAndNewIndex tryGetCountryCallingCodeAndNewIndex( String str, boolean acceptThailandCase) { // Rough regexp: // ^[^0-9*#+]*((\+|0(0|11)\d\d?|166) [^0-9*#+] $ // 0 1 2 3 45 6 7 89 // // In all the states, this function ignores separator characters. // "166" is the special case for the call from Thailand to the US. Uguu! int state = 0; int ccc = 0; final int length = str.length(); for (int i = 0 ; i < length ; i++ ) { char ch = str.charAt(i); switch (state) { case 0: if (ch == '+') state = 1; else if (ch == '0') state = 2; else if (ch == '1') { if (acceptThailandCase) { state = 8; } else { return null; } } else if (isDialable(ch)) { return null; } break; case 2: if (ch == '0') state = 3; else if (ch == '1') state = 4; else if (isDialable(ch)) { return null; } break; case 4: if (ch == '1') state = 5; else if (isDialable(ch)) { return null; } break; case 1: case 3: case 5: case 6: case 7: { int ret = tryGetISODigit(ch); if (ret > 0) { ccc = ccc * 10 + ret; if (ccc >= 100 || isCountryCallingCode(ccc)) { return new CountryCallingCodeAndNewIndex(ccc, i + 1); } if (state == 1 || state == 3 || state == 5) { state = 6; } else { state++; } } else if (isDialable(ch)) { return null; } } break; case 8: if (ch == '6') state = 9; else if (isDialable(ch)) { return null; } break; case 9: if (ch == '6') { return new CountryCallingCodeAndNewIndex(66, i + 1); } else { return null; } default: return null; } } return null; }
Currently this function simply ignore the first digit assuming it is trunk prefix. Actually trunk prefix is different in each country. e.g. "+79161234567" equals "89161234567" (Russian trunk digit is 8) "+33123456789" equals "0123456789" (French trunk digit is 0)
/** * Currently this function simply ignore the first digit assuming it is * trunk prefix. Actually trunk prefix is different in each country. * * e.g. * "+79161234567" equals "89161234567" (Russian trunk digit is 8) * "+33123456789" equals "0123456789" (French trunk digit is 0) * */
private static int tryGetTrunkPrefixOmittedIndex(String str, int currentIndex) { int length = str.length(); for (int i = currentIndex ; i < length ; i++) { final char ch = str.charAt(i); if (tryGetISODigit(ch) >= 0) { return i + 1; } else if (isDialable(ch)) { return -1; } } return -1; }
Return true if the prefix of "str" is "ignorable". Here, "ignorable" means that "str" has only one digit and separator characters. The one digit is assumed to be trunk prefix.
/** * Return true if the prefix of "str" is "ignorable". Here, "ignorable" means * that "str" has only one digit and separator characters. The one digit is * assumed to be trunk prefix. */
private static boolean checkPrefixIsIgnorable(final String str, int forwardIndex, int backwardIndex) { boolean trunk_prefix_was_read = false; while (backwardIndex >= forwardIndex) { if (tryGetISODigit(str.charAt(backwardIndex)) >= 0) { if (trunk_prefix_was_read) { // More than one digit appeared, meaning that "a" and "b" // is different. return false; } else { // Ignore just one digit, assuming it is trunk prefix. trunk_prefix_was_read = true; } } else if (isDialable(str.charAt(backwardIndex))) { // Trunk prefix is a digit, not "*", "#"... return false; } backwardIndex--; } return true; }
Returns Default voice subscription Id.
/** * Returns Default voice subscription Id. */
private static int getDefaultVoiceSubId() { return SubscriptionManager.getDefaultVoiceSubscriptionId(); } //==== End of utility methods used only in compareStrictly() ===== /* * The config held calling number conversion map, expected to convert to emergency number. */ private static String[] sConvertToEmergencyMap = null;
Converts to emergency number based on the conversion map. The conversion map is declared as config_convert_to_emergency_number_map.
Params:
  • context – a context to use for accessing resources
Returns:The converted emergency number if the number matches conversion map, otherwise original number.
@hide
/** * Converts to emergency number based on the conversion map. * The conversion map is declared as config_convert_to_emergency_number_map. * * @param context a context to use for accessing resources * @return The converted emergency number if the number matches conversion map, * otherwise original number. * * @hide */
public static String convertToEmergencyNumber(Context context, String number) { if (context == null || TextUtils.isEmpty(number)) { return number; } String normalizedNumber = normalizeNumber(number); // The number is already emergency number. Skip conversion. if (isEmergencyNumber(normalizedNumber)) { return number; } if (sConvertToEmergencyMap == null) { sConvertToEmergencyMap = context.getResources().getStringArray( com.android.internal.R.array.config_convert_to_emergency_number_map); } // The conversion map is not defined (this is default). Skip conversion. if (sConvertToEmergencyMap == null || sConvertToEmergencyMap.length == 0 ) { return number; } for (String convertMap : sConvertToEmergencyMap) { if (DBG) log("convertToEmergencyNumber: " + convertMap); String[] entry = null; String[] filterNumbers = null; String convertedNumber = null; if (!TextUtils.isEmpty(convertMap)) { entry = convertMap.split(":"); } if (entry != null && entry.length == 2) { convertedNumber = entry[1]; if (!TextUtils.isEmpty(entry[0])) { filterNumbers = entry[0].split(","); } } // Skip if the format of entry is invalid if (TextUtils.isEmpty(convertedNumber) || filterNumbers == null || filterNumbers.length == 0) { continue; } for (String filterNumber : filterNumbers) { if (DBG) log("convertToEmergencyNumber: filterNumber = " + filterNumber + ", convertedNumber = " + convertedNumber); if (!TextUtils.isEmpty(filterNumber) && filterNumber.equals(normalizedNumber)) { if (DBG) log("convertToEmergencyNumber: Matched. Successfully converted to: " + convertedNumber); return convertedNumber; } } } return number; } }