summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--telephony/java/android/telephony/PhoneNumberUtils.java612
1 files changed, 492 insertions, 120 deletions
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index 2672c6d..2d8afb7 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -111,6 +111,11 @@ public class PhoneNumberUtils
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
@@ -293,6 +298,21 @@ public class PhoneNumberUtils
}
/**
+ * 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);
+ }
+
+ /**
+ * @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.
*
@@ -301,10 +321,13 @@ public class PhoneNumberUtils
* - handles common trunk prefixes and international prefixes
* (basically, everything except the Russian trunk prefix)
*
- * Tolerates nulls
+ * 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
- compare(String a, String b) {
+ compareLoosely(String a, String b) {
int ia, ib;
int matched;
@@ -391,6 +414,160 @@ public class PhoneNumberUtils
}
/**
+ * @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
*
@@ -475,54 +652,6 @@ public class PhoneNumberUtils
}
/**
- * Phone numbers are stored in "lookup" form in the database
- * as reversed strings to allow for caller ID lookup
- *
- * This method takes a phone number and makes a valid SQL "LIKE"
- * string that will match the lookup form
- *
- */
- /** all of a up to len must be an international prefix or
- * separators/non-dialing digits
- */
- private static boolean
- matchIntlPrefix(String a, int len) {
- /* '([^0-9*#+pwn]\+[^0-9*#+pwn] | [^0-9*#+pwn]0(0|11)[^0-9*#+pwn] )$' */
- /* 0 1 2 3 45 */
-
- int state = 0;
- for (int i = 0 ; i < len ; i++) {
- char c = a.charAt(i);
-
- switch (state) {
- case 0:
- if (c == '+') state = 1;
- else if (c == '0') state = 2;
- else if (isNonSeparator(c)) return false;
- break;
-
- case 2:
- if (c == '0') state = 3;
- else if (c == '1') state = 4;
- else if (isNonSeparator(c)) return false;
- break;
-
- case 4:
- if (c == '1') state = 5;
- else if (isNonSeparator(c)) return false;
- break;
-
- default:
- if (isNonSeparator(c)) return false;
- break;
-
- }
- }
-
- return state == 1 || state == 3 || state == 5;
- }
-
- /**
* 3GPP TS 24.008 10.5.4.7
* Called Party BCD Number
*
@@ -835,76 +964,6 @@ public class PhoneNumberUtils
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 */
@@ -1553,4 +1612,317 @@ public class PhoneNumberUtils
}
return retStr;
}
+
+ //===== Begining of utility methods used in compareLoosely() =====
+
+ /**
+ * Phone numbers are stored in "lookup" form in the database
+ * as reversed strings to allow for caller ID lookup
+ *
+ * This method takes a phone number and makes a valid SQL "LIKE"
+ * string that will match the lookup form
+ *
+ */
+ /** all of a up to len must be an international prefix or
+ * separators/non-dialing digits
+ */
+ private static boolean
+ matchIntlPrefix(String a, int len) {
+ /* '([^0-9*#+pwn]\+[^0-9*#+pwn] | [^0-9*#+pwn]0(0|11)[^0-9*#+pwn] )$' */
+ /* 0 1 2 3 45 */
+
+ int state = 0;
+ for (int i = 0 ; i < len ; i++) {
+ char c = a.charAt(i);
+
+ switch (state) {
+ case 0:
+ if (c == '+') state = 1;
+ else if (c == '0') state = 2;
+ else if (isNonSeparator(c)) return false;
+ break;
+
+ case 2:
+ if (c == '0') state = 3;
+ else if (c == '1') state = 4;
+ else if (isNonSeparator(c)) return false;
+ break;
+
+ case 4:
+ if (c == '1') state = 5;
+ else if (isNonSeparator(c)) return false;
+ break;
+
+ default:
+ if (isNonSeparator(c)) return false;
+ break;
+
+ }
+ }
+
+ return state == 1 || state == 3 || state == 5;
+ }
+
+ /** all of 'a' up to len must be a (+|00|011)country code)
+ * We're fast and loose with the country code. Any \d{1,3} matches */
+ private static boolean
+ matchIntlPrefixAndCC(String a, int len) {
+ /* [^0-9*#+pwn]*(\+|0(0|11)\d\d?\d? [^0-9*#+pwn] $ */
+ /* 0 1 2 3 45 6 7 8 */
+
+ int state = 0;
+ for (int i = 0 ; i < len ; i++ ) {
+ char c = a.charAt(i);
+
+ switch (state) {
+ case 0:
+ if (c == '+') state = 1;
+ else if (c == '0') state = 2;
+ else if (isNonSeparator(c)) return false;
+ break;
+
+ case 2:
+ if (c == '0') state = 3;
+ else if (c == '1') state = 4;
+ else if (isNonSeparator(c)) return false;
+ break;
+
+ case 4:
+ if (c == '1') state = 5;
+ else if (isNonSeparator(c)) return false;
+ break;
+
+ case 1:
+ case 3:
+ case 5:
+ if (isISODigit(c)) state = 6;
+ else if (isNonSeparator(c)) return false;
+ break;
+
+ case 6:
+ case 7:
+ if (isISODigit(c)) state++;
+ else if (isNonSeparator(c)) return false;
+ break;
+
+ default:
+ if (isNonSeparator(c)) return false;
+ }
+ }
+
+ return state == 6 || state == 7 || state == 8;
+ }
+
+ /** all of 'a' up to len must match non-US trunk prefix ('0') */
+ private static boolean
+ matchTrunkPrefix(String a, int len) {
+ boolean found;
+
+ found = false;
+
+ for (int i = 0 ; i < len ; i++) {
+ char c = a.charAt(i);
+
+ if (c == '0' && !found) {
+ found = true;
+ } else if (isNonSeparator(c)) {
+ return false;
+ }
+ }
+
+ return found;
+ }
+
+ //===== End of utility methods used only in compareLoosely() =====
+
+ //===== Beggining of utility methods used only in compareStrictly() ====
+
+ /*
+ * If true, the number is country calling code.
+ */
+ private static final boolean COUNTLY_CALLING_CALL[] = {
+ true, true, false, false, false, false, false, true, false, false,
+ false, false, false, false, false, false, false, false, false, false,
+ true, false, false, false, false, false, false, true, true, false,
+ true, true, true, true, true, false, true, false, false, true,
+ true, false, false, true, true, true, true, true, true, true,
+ false, true, true, true, true, true, true, true, true, false,
+ true, true, true, true, true, true, true, false, false, false,
+ false, false, false, false, false, false, false, false, false, false,
+ false, true, true, true, true, false, true, false, false, true,
+ true, true, true, true, true, true, false, false, true, false,
+ };
+ private static final int CCC_LENGTH = COUNTLY_CALLING_CALL.length;
+
+ /**
+ * @return true when input is valid Country Calling Code.
+ */
+ private static boolean isCountryCallingCode(int countryCallingCodeCandidate) {
+ return countryCallingCodeCandidate > 0 && countryCallingCodeCandidate < CCC_LENGTH &&
+ COUNTLY_CALLING_CALL[countryCallingCodeCandidate];
+ }
+
+ /**
+ * Returns interger corresponding to the input if input "ch" is
+ * ISO-LATIN characters 0-9.
+ * Returns -1 otherwise
+ */
+ private static int tryGetISODigit(char ch) {
+ if ('0' <= ch && ch <= '9') {
+ return ch - '0';
+ } else {
+ return -1;
+ }
+ }
+
+ private static class CountryCallingCodeAndNewIndex {
+ public final int countryCallingCode;
+ public final int newIndex;
+ public CountryCallingCodeAndNewIndex(int countryCode, int newIndex) {
+ this.countryCallingCode = countryCode;
+ this.newIndex = newIndex;
+ }
+ }
+
+ /*
+ * Note that this function does not strictly care the country calling code with
+ * 3 length (like Morocco: +212), assuming it is enough to use the first two
+ * digit to compare two phone numbers.
+ */
+ private static CountryCallingCodeAndNewIndex tryGetCountryCallingCodeAndNewIndex(
+ String str, boolean acceptThailandCase) {
+ // Rough regexp:
+ // ^[^0-9*#+]*((\+|0(0|11)\d\d?|166) [^0-9*#+] $
+ // 0 1 2 3 45 6 7 89
+ //
+ // In all the states, this function ignores separator characters.
+ // "166" is the special case for the call from Thailand to the US. Uguu!
+ int state = 0;
+ int ccc = 0;
+ final int length = str.length();
+ for (int i = 0 ; i < length ; i++ ) {
+ char ch = str.charAt(i);
+ switch (state) {
+ case 0:
+ if (ch == '+') state = 1;
+ else if (ch == '0') state = 2;
+ else if (ch == '1') {
+ if (acceptThailandCase) {
+ state = 8;
+ } else {
+ return null;
+ }
+ } else if (isDialable(ch)) {
+ return null;
+ }
+ break;
+
+ case 2:
+ if (ch == '0') state = 3;
+ else if (ch == '1') state = 4;
+ else if (isDialable(ch)) {
+ return null;
+ }
+ break;
+
+ case 4:
+ if (ch == '1') state = 5;
+ else if (isDialable(ch)) {
+ return null;
+ }
+ break;
+
+ case 1:
+ case 3:
+ case 5:
+ case 6:
+ case 7:
+ {
+ int ret = tryGetISODigit(ch);
+ if (ret > 0) {
+ ccc = ccc * 10 + ret;
+ if (ccc >= 100 || isCountryCallingCode(ccc)) {
+ return new CountryCallingCodeAndNewIndex(ccc, i + 1);
+ }
+ if (state == 1 || state == 3 || state == 5) {
+ state = 6;
+ } else {
+ state++;
+ }
+ } else if (isDialable(ch)) {
+ return null;
+ }
+ }
+ break;
+ case 8:
+ if (ch == '6') state = 9;
+ else if (isDialable(ch)) {
+ return null;
+ }
+ break;
+ case 9:
+ if (ch == '6') {
+ return new CountryCallingCodeAndNewIndex(66, i + 1);
+ } else {
+ return null;
+ }
+ default:
+ return null;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Currently this function simply ignore the first digit assuming it is
+ * trunk prefix. Actually trunk prefix is different in each country.
+ *
+ * e.g.
+ * "+79161234567" equals "89161234567" (Russian trunk digit is 8)
+ * "+33123456789" equals "0123456789" (French trunk digit is 0)
+ *
+ */
+ private static int tryGetTrunkPrefixOmittedIndex(String str, int currentIndex) {
+ int length = str.length();
+ for (int i = currentIndex ; i < length ; i++) {
+ final char ch = str.charAt(i);
+ if (tryGetISODigit(ch) >= 0) {
+ return i + 1;
+ } else if (isDialable(ch)) {
+ return -1;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Return true if the prefix of "str" is "ignorable". Here, "ignorable" means
+ * that "str" has only one digit and separater characters. The one digit is
+ * assumed to be trunk prefix.
+ */
+ private static boolean checkPrefixIsIgnorable(final String str,
+ int forwardIndex, int backwardIndex) {
+ boolean trunk_prefix_was_read = false;
+ while (backwardIndex >= forwardIndex) {
+ if (tryGetISODigit(str.charAt(backwardIndex)) >= 0) {
+ if (trunk_prefix_was_read) {
+ // More than one digit appeared, meaning that "a" and "b"
+ // is different.
+ return false;
+ } else {
+ // Ignore just one digit, assuming it is trunk prefix.
+ trunk_prefix_was_read = true;
+ }
+ } else if (isDialable(str.charAt(backwardIndex))) {
+ // Trunk prefix is a digit, not "*", "#"...
+ return false;
+ }
+ backwardIndex--;
+ }
+
+ return true;
+ }
+
+ //==== End of utility methods used only in compareStrictly() =====
}