|
Android example source code file (PhoneNumberUtils.java)
The PhoneNumberUtils.java Android example source code/*
* 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 android.content.Context;
import android.content.Intent;
import android.database.Cursor;
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;
/**
* Various utilities for dealing with phone number strings.
*/
public class PhoneNumberUtils
{
/*
* 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;
/*
* 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 */
public static boolean
isISODigit (char c) {
return c >= '0' && c <= '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 */
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) */
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 */
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)
*/
public final static boolean
isStartsPostDial (char c) {
return c == PAUSE || c == WAIT;
}
/** 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.
*
* @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 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();
}
// TODO: We don't check for SecurityException here (requires
// READ_PHONE_STATE permission).
if (scheme.equals("voicemail")) {
return TelephonyManager.getDefault().getVoiceMailNumber();
}
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;
}
final Cursor c = context.getContentResolver().query(uri, new String[] {
phoneColumn
}, null, null, null);
if (c != null) {
try {
if (c.moveToFirst()) {
number = c.getString(c.getColumnIndex(phoneColumn));
}
} 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.
*
* 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;
}
}
int pos = addPlusChar(phoneNumber);
if (pos >= 0 && ret.length() > pos) {
ret.insert(pos, '+');
}
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);
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;
}
}
private static void log(String msg) {
Log.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;
}
}
/** GSM codes
* Finds if a GSM code includes the international prefix (+).
*
* @param number the number to dial.
*
* @return the position where the + char will be inserted, -1 if the GSM code was not found.
*/
private static int
addPlusChar(String number) {
int pos = -1;
if (number.startsWith(CLIR_OFF)) {
pos = CLIR_OFF.length() - 1;
}
if (number.startsWith(CLIR_ON)) {
pos = CLIR_ON.length() - 1;
}
return pos;
}
/**
* 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)<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
*/
public static boolean
compareStrictly(String a, String b) {
return compareStrictly(a, b, true);
}
/**
* @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 equalt 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
*/
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
*/
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
*/
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;
}
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) {
// 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 is a
// 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) {
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 commense 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;
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;
} else {
throw new RuntimeException ("invalid char for BCD " + c);
}
}
/**
* Return true iff the network portion of <code>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 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;
}
//================ 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:Shuold 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:
*
* <p>
|
... this post is sponsored by my books ... | |
#1 New Release! |
FP Best Seller |
Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.
A percentage of advertising revenue from
pages under the /java/jwarehouse
URI on this website is
paid back to open source projects.