null
if the number cannot be found.
*/
public static String getNumberFromIntent(Intent intent, Context context) {
String number = null;
Uri uri = intent.getData();
String scheme = uri.getScheme();
if (scheme.equals("tel")) {
return uri.getSchemeSpecificPart();
}
if (scheme.equals("voicemail")) {
return TelephonyManager.getDefault().getVoiceMailNumber();
}
if (context == null) {
return null;
}
String type = intent.resolveType(context);
Cursor c = context.getContentResolver().query(
uri, new String[]{ Contacts.People.Phones.NUMBER },
null, null, null);
if (c != null) {
try {
if (c.moveToFirst()) {
number = c.getString(
c.getColumnIndex(Contacts.People.Phones.NUMBER));
}
} finally {
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.
*
* Allows + only in the first position in the result string.
*
* 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);
boolean firstCharAdded = false;
for (int i = 0; i < len; i++) {
char c = phoneNumber.charAt(i);
if (isDialable(c) && (c != '+' || !firstCharAdded)) {
firstCharAdded = true;
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);
if (isNonSeparator(c)) {
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;
}
}
/** 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.
*
* - Compares from right to left
* - requires MIN_MATCH (5) characters to match
* - handles common trunk prefixes and international prefixes
* (basically, everything except the Russian trunk prefix)
*
* Tolerates nulls
*/
public static boolean
compare(String a, String b) {
int ia, ib;
int matched;
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;
}
cb = b.charAt(ib);
if (!isDialable(cb)) {
ib--;
skipCmp = true;
}
if (!skipCmp) {
if (cb != ca && ca != WILD && cb != WILD) {
break;
}
ia--; ib--; matched++;
}
}
if (matched < MIN_MATCH) {
int aLen = a.length();
// if the input strings match, but their lengths < MIN_MATCH,
// treat them as equal.
if (aLen == b.length() && aLen == 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;
}
/**
* 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 internationl 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;
}
/** 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;
}
/** 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;
}
//================ 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
"NP", // 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();
}
/**
* 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();
// Check for the NANP countries
int length = NANP_COUNTRIES.length;
for (int i = 0; i < length; i++) {
if (NANP_COUNTRIES[i].equals(country)) {
return FORMAT_NANP;
}
}
if (locale.equals(Locale.JAPAN)) {
return FORMAT_JAPAN;
}
return FORMAT_UNKNOWN;
}
/**
* Formats a phone number in-place. Currently only supports NANP formatting.
*
* @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 +
* 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
*