null
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
*/
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
*/
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.
* @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 #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
*/
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 */
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)
*/
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
*/
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.
*/
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.
*/
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);
}
/**
* @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
*/
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)address
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.
*/
public static byte[]
networkPortionToCalledPartyBCD(String s) {
String networkPortion = extractNetworkPortion(s);
return numberToCalledPartyBCDHelper(networkPortion, false);
}
/**
* 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);
}
/**
* 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
*/
public static byte[]
numberToCalledPartyBCD(String number) {
return numberToCalledPartyBCDHelper(number, false);
}
/**
* If includeLength is true, prepend a one-byte length value to
* the return array.
*/
private static byte[]
numberToCalledPartyBCDHelper(String number, boolean includeLength) {
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) & 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 */
public static final int FORMAT_UNKNOWN = 0;
/** NANP formatting */
public static final int FORMAT_NANP = 1;
/** Japanese formatting */
public static final int FORMAT_JAPAN = 2;
/** 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";
/**
* 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
* {@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.
*
* @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 {@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
*
* 03-xxxx-xxxx
* 090-xxxx-xxxx
* 0120-xxx-xxx
* +81-3-xxxx-xxxx
* +81-90-xxxx-xxxx
*
* 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. * * @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; } /** * 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. * * @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); /** * 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. */ if (KOREA_ISO_COUNTRY_CODE.equals(defaultCountryIso) && (pn.getCountryCode() == util.getCountryCodeForRegion(KOREA_ISO_COUNTRY_CODE)) && (pn.getCountryCodeSource() == PhoneNumber.CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN)) { 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.
*
* @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.
*
* @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.
*
* @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.
// Czech Republic: 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.
*
* @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.
*
* @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.)
*
* @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.)
*
* @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).
*
* @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).
*
* @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.
*
* @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.
*
* @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.)
*
* @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.)
*
* @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).
*
* @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).
*
* @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.getSlotId(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) {
ShortNumberUtil util = new ShortNumberUtil();
if (useExactMatch) {
return util.isEmergencyNumber(number, defaultCountryIso);
} else {
return util.connectsToEmergencyNumber(number, defaultCountryIso);
}
}
return false;
}
/**
* 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.
*
* @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.)
*
* @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.)
*
* @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().
*
* @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().
*
* @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.
*
* @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.
*
* @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.
*
* @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.
*
* @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.
*/
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
*
* @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.
*
* @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
*
*
* @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 {@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 {@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 {@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 {@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 {@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, 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.isISODigit(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
*/
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