PhoneNumberUtils.java (frameworks\base\telephony\java\android\telephony)
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.ShortNumberUtil;
import com.android.i18n.phonenumbers.Phonemetadata.PhoneMetadata;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.location.CountryDetector;
import android.net.Uri;
import android.os.SystemProperties;
import android.provider.Contacts;
import android.provider.ContactsContract;
import android.text.Editable;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseIntArray;
import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_ISO_COUNTRY;
import static com.android.internal.telephony.TelephonyProperties.PROPERTY_IDP_STRING;
import static com.android.internal.telephony.TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/// M : [mtk04070][111116][ALPS00093395]Use Gemini object @{
import static com.android.internal.telephony.PhoneConstants.GEMINI_SIM_1;
import static com.android.internal.telephony.PhoneConstants.GEMINI_DEFAULT_SIM_PROP;
import static com.android.internal.telephony.PhoneConstants.GEMINI_SIM_ID_KEY;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import com.mediatek.common.featureoption.FeatureOption;
import com.mediatek.common.MediatekClassFactory;
import com.mediatek.common.telephony.IPhoneNumberExt;
import com.android.internal.telephony.PhoneConstants;
/// @}
import android.os.ServiceManager;
import com.android.internal.telephony.ITelephony;
/**
* Various utilities for dealing with phone number strings.
*/
public class PhoneNumberUtils
/**
* 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
*/
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
*/
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)"
*
* @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
*
* FIXME(mkf) support alphanumeric address type
* currently implemented in SMSMessage.getAddress()
*/
public static String calledPartyBCDToString (byte[] bytes, int offset, int length) {
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;
}
// 解析 byte中的 字符 取出BCD ,编码的秘密就在这个函数里面
internalCalledPartyBCDFragmentToString(ret, bytes, offset + 1, length - 1);
if (prependPlus && ret.length() == 0) {
// If the only thing there is a prepended plus, return ""
return "";
}
if (prependPlus) {
/// M: [mtk04070][111116][ALPS00093395]Replace origin codes with prependPlusToNumber method. @{
ret = new StringBuilder(mPhoneNumberExt.prependPlusToNumber(ret.toString()));
/// @}
}
return ret.toString(); // 返回解析得到的字符串
}
private static void internalCalledPartyBCDFragmentToString( StringBuilder sb, byte [] bytes, int offset, int length) {
for (int i = offset ; i < length + offset ; i++) {
byte b;
char c;
c = bcdToChar((byte)(bytes[i] & 0xf));
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);
if (c == 0) {
return;
}
sb.append(c);
}
}
/**
* 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) {
StringBuilder ret = new StringBuilder(length * 2);
internalCalledPartyBCDFragmentToString(ret, bytes, offset, length);
return ret.toString();
}
/** returns 0 on invalid value */
private static char
bcdToChar(byte b) {
if (b < 0xa) {
return (char)('0' + b);
} else switch (b) {
case 0xa: return '*';
case 0xb: return '#';
case 0xc: return PAUSE;
case 0xd: return WILD;
/// M: add wait for ANR @{
case 0xe: return WAIT;
/// @}
default: return 0;
}
}
private static int
charToBCD(char c) {
if (c >= '0' && c <= '9') {
return c - '0';
} else if (c == '*') {
return 0xa;
} else if (c == '#') {
return 0xb;
} else if (c == PAUSE) {
return 0xc;
} else if (c == WILD) {
return 0xd;
/// M: add wait for ANR @{
} else if (c == WAIT) {
return 0xe;
/// @}
} else {
throw new RuntimeException ("invalid char for BCD " + 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.
*/
public static boolean isWellFormedSmsAddress(String address) {
/// M: [mtk04070][120104][ALPS00109412]Solve "can't send MMS with MSISDN in international format". @{
//Merge from ALPS00089029
//if (!isDialable(address)) {
// return false;
//}
/// @}
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
};
/**
* 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
*/
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 TODO: Should be unhidden.
*/
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
*/
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]
*/
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
*
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;
}
}
}