/* * Copyright 2009, 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. */ #include #include namespace android { /* Generated by the following Python script. Values of country calling codes are from http://en.wikipedia.org/wiki/List_of_country_calling_codes #!/usr/bin/python import sys ccc_set_2digits = set([0, 1, 7, 20, 27, 28, 30, 31, 32, 33, 34, 36, 39, 40, 43, 44, 45, 46, 47, 48, 49, 51, 52, 53, 54, 55, 56, 57, 58, 60, 61, 62, 63, 64, 65, 66, 81, 82, 83, 84, 86, 89, 90, 91, 92, 93, 94, 95, 98]) ONE_LINE_NUM = 10 for i in xrange(100): if i % ONE_LINE_NUM == 0: sys.stdout.write(' ') if i in ccc_set_2digits: included = 'true' else: included = 'false' sys.stdout.write(included + ',') if ((i + 1) % ONE_LINE_NUM) == 0: sys.stdout.write('\n') else: sys.stdout.write(' ') */ static bool two_length_country_code_map[100] = { 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, }; #define ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0])) /** * Returns true if "ccc_candidate" expresses (part of ) some country calling * code. * Returns false otherwise. */ static bool isCountryCallingCode(int ccc_candidate) { return ccc_candidate > 0 && ccc_candidate < (int)ARRAY_SIZE(two_length_country_code_map) && two_length_country_code_map[ccc_candidate]; } /** * Returns interger corresponding to the input if input "ch" is * ISO-LATIN characters 0-9. * Returns -1 otherwise */ static int tryGetISODigit (char ch) { if ('0' <= ch && ch <= '9') { return ch - '0'; } else { return -1; } } /** * True if ch is ISO-LATIN characters 0-9, *, # , + * Note this method current does not account for the WILD char 'N' */ static bool isDialable(char ch) { return ('0' <= ch && ch <= '9') || ch == '*' || ch == '#' || ch == '+'; } /** Returns true if ch is not dialable or alpha char */ static bool isSeparator(char ch) { return !isDialable(ch) && (isalpha(ch) == 0); } /** * Try to store the pointer to "new_ptr" which does not have trunk prefix. * * 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) * */ static bool tryGetTrunkPrefixOmittedStr(const char *str, size_t len, const char **new_ptr, size_t *new_len) { for (size_t i = 0 ; i < len ; i++) { char ch = str[i]; if (tryGetISODigit(ch) >= 0) { if (new_ptr != NULL) { *new_ptr = str + i + 1; } if (new_len != NULL) { *new_len = len - (i + 1); } return true; } else if (isDialable(ch)) { return false; } } return false; } /* * 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. */ static int tryGetCountryCallingCode(const char *str, size_t len, const char **new_ptr, size_t *new_len, bool accept_thailand_case) { // 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. Ugu! int state = 0; int ccc = 0; for (size_t i = 0 ; i < len ; i++ ) { char ch = str[i]; switch (state) { case 0: if (ch == '+') state = 1; else if (ch == '0') state = 2; else if (ch == '1') { if (accept_thailand_case) { state = 8; } else { return -1; } } else if (isDialable(ch)) return -1; break; case 2: if (ch == '0') state = 3; else if (ch == '1') state = 4; else if (isDialable(ch)) return -1; break; case 4: if (ch == '1') state = 5; else if (isDialable(ch)) return -1; 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)) { if (new_ptr != NULL) { *new_ptr = str + i + 1; } if (new_len != NULL) { *new_len = len - (i + 1); } return ccc; } if (state == 1 || state == 3 || state == 5) { state = 6; } else { state++; } } else if (isDialable(ch)) { return -1; } } break; case 8: if (ch == '6') state = 9; else if (isDialable(ch)) return -1; break; case 9: if (ch == '6') { if (new_ptr != NULL) { *new_ptr = str + i + 1; } if (new_len != NULL) { *new_len = len - (i + 1); } return 66; } else { return -1; } break; default: return -1; } } return -1; } /** * Return true if the prefix of "ch" is "ignorable". Here, "ignorable" means * that "ch" has only one digit and separater characters. The one digit is * assumed to be trunk prefix. */ static bool checkPrefixIsIgnorable(const char* ch, int i) { bool trunk_prefix_was_read = false; while (i >= 0) { if (tryGetISODigit(ch[i]) >= 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(ch[i])) { // Trunk prefix is a digit, not "*", "#"... return false; } i--; } return true; } /** * Compare phone numbers a and b, return true if they're identical * enough for caller ID purposes. * * Assume NULL as 0-length string. * * Detailed information: * Currently (as of 2009-06-12), we cannot depend on the locale given from the * OS. For example, current Android does not accept "en_JP", meaning * "the display language is English but the phone should be in Japan", but * en_US, es_US, etc. So we cannot identify which digit is valid trunk prefix * in the country where the phone is used. More specifically, "880-1234-1234" * is not valid phone number in Japan since the trunk prefix in Japan is not 8 * but 0 (correct number should be "080-1234-1234"), while Russian trunk prefix * is 8. Also, we cannot know whether the country where users live has trunk * prefix itself. So, we cannot determine whether "+81-80-1234-1234" is NOT * same as "880-1234-1234" (while "+81-80-1234-1234" is same as "080-1234-1234" * and we can determine "880-1234-1234" is different from "080-1234-1234"). * * In the future, we should handle trunk prefix more correctly, but as of now, * we just ignore it... */ static bool phone_number_compare_inter(const char* const org_a, const char* const org_b, bool accept_thailand_case) { const char* a = org_a; const char* b = org_b; size_t len_a = 0; size_t len_b = 0; if (a == NULL) { a = ""; } else { len_a = strlen(a); } if (b == NULL) { b = ""; } else { len_b = strlen(b); } const char* tmp_a = NULL; const char* tmp_b = NULL; size_t tmp_len_a = len_a; size_t tmp_len_b = len_b; int ccc_a = tryGetCountryCallingCode(a, len_a, &tmp_a, &tmp_len_a, accept_thailand_case); int ccc_b = tryGetCountryCallingCode(b, len_b, &tmp_b, &tmp_len_b, accept_thailand_case); bool both_have_ccc = false; bool ok_to_ignore_prefix = true; bool trunk_prefix_is_omitted_a = false; bool trunk_prefix_is_omitted_b = false; if (ccc_a >= 0 && ccc_b >= 0) { if (ccc_a != ccc_b) { // 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) ok_to_ignore_prefix = false; both_have_ccc = true; } else if (ccc_a < 0 && ccc_b < 0) { // When both do not have ccc, do not ignore trunk prefix. Without this, // "123123" becomes same as "0123123" ok_to_ignore_prefix = false; } else { if (ccc_a < 0) { tryGetTrunkPrefixOmittedStr(a, len_a, &tmp_a, &tmp_len_a); trunk_prefix_is_omitted_a = true; } if (ccc_b < 0) { tryGetTrunkPrefixOmittedStr(b, len_b, &tmp_b, &tmp_len_b); trunk_prefix_is_omitted_b = true; } } if (tmp_a != NULL) { a = tmp_a; len_a = tmp_len_a; } if (tmp_b != NULL) { b = tmp_b; len_b = tmp_len_b; } int i_a = len_a - 1; int i_b = len_b - 1; while (i_a >= 0 && i_b >= 0) { bool skip_compare = false; char ch_a = a[i_a]; char ch_b = b[i_b]; if (isSeparator(ch_a)) { i_a--; skip_compare = true; } if (isSeparator(ch_b)) { i_b--; skip_compare = true; } if (!skip_compare) { if (ch_a != ch_b) { return false; } i_a--; i_b--; } } if (ok_to_ignore_prefix) { if ((trunk_prefix_is_omitted_a && i_a >= 0) || !checkPrefixIsIgnorable(a, i_a)) { if (accept_thailand_case) { // 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 phone_number_compare_inter(org_a, org_b, false); } else { return false; } } if ((trunk_prefix_is_omitted_b && i_b >= 0) || !checkPrefixIsIgnorable(b, i_b)) { if (accept_thailand_case) { return phone_number_compare_inter(org_a, org_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 bool may_be_namp = !both_have_ccc; while (i_a >= 0) { const char ch_a = a[i_a]; if (isDialable(ch_a)) { if (may_be_namp && tryGetISODigit(ch_a) == 1) { may_be_namp = false; } else { return false; } } i_a--; } while (i_b >= 0) { const char ch_b = b[i_b]; if (isDialable(ch_b)) { if (may_be_namp && tryGetISODigit(ch_b) == 1) { may_be_namp = false; } else { return false; } } i_b--; } } return true; } bool phone_number_compare_strict(const char* a, const char* b) { return phone_number_compare_inter(a, b, true); } /** * Imitates the Java method PhoneNumberUtils.getStrippedReversed. * Used for API compatibility with Android 1.6 and earlier. */ bool phone_number_stripped_reversed_inter(const char* in, char* out, const int len, int *outlen) { int in_len = strlen(in); int out_len = 0; bool have_seen_plus = false; for (int i = in_len; --i >= 0;) { char c = in[i]; if ((c >= '0' && c <= '9') || c == '*' || c == '#' || c == 'N') { if (out_len < len) { out[out_len++] = c; } } else { switch (c) { case '+': if (!have_seen_plus) { if (out_len < len) { out[out_len++] = c; } have_seen_plus = true; } break; case ',': case ';': out_len = 0; break; } } } *outlen = out_len; return true; } } // namespace android