Home | History | Annotate | Download | only in android
      1 /*
      2  * Copyright 2009, The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *     http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #include <ctype.h>
     18 #include <string.h>
     19 
     20 namespace android {
     21 
     22 /* Generated by the following Python script. Values of country calling codes
     23    are from http://en.wikipedia.org/wiki/List_of_country_calling_codes
     24 
     25 #!/usr/bin/python
     26 import sys
     27 ccc_set_2digits = set([0, 1, 7,
     28                        20, 27, 28, 30, 31, 32, 33, 34, 36, 39, 40, 43, 44, 45,
     29                        46, 47, 48, 49, 51, 52, 53, 54, 55, 56, 57, 58, 60, 61,
     30                        62, 63, 64, 65, 66, 81, 82, 83, 84, 86, 89, 90, 91, 92,
     31                        93, 94, 95, 98])
     32 
     33 ONE_LINE_NUM = 10
     34 
     35 for i in xrange(100):
     36   if i % ONE_LINE_NUM == 0:
     37     sys.stdout.write('    ')
     38   if i in ccc_set_2digits:
     39     included = 'true'
     40   else:
     41     included = 'false'
     42   sys.stdout.write(included + ',')
     43   if ((i + 1) % ONE_LINE_NUM) == 0:
     44     sys.stdout.write('\n')
     45   else:
     46     sys.stdout.write(' ')
     47 */
     48 static bool two_length_country_code_map[100] = {
     49     true, true, false, false, false, false, false, true, false, false,
     50     false, false, false, false, false, false, false, false, false, false,
     51     true, false, false, false, false, false, false, true, true, false,
     52     true, true, true, true, true, false, true, false, false, true,
     53     true, false, false, true, true, true, true, true, true, true,
     54     false, true, true, true, true, true, true, true, true, false,
     55     true, true, true, true, true, true, true, false, false, false,
     56     false, false, false, false, false, false, false, false, false, false,
     57     false, true, true, true, true, false, true, false, false, true,
     58     true, true, true, true, true, true, false, false, true, false,
     59 };
     60 
     61 #define ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0]))
     62 
     63 /**
     64  * Returns true if "ccc_candidate" expresses (part of ) some country calling
     65  * code.
     66  * Returns false otherwise.
     67  */
     68 static bool isCountryCallingCode(int ccc_candidate) {
     69     return ccc_candidate > 0 &&
     70             ccc_candidate < (int)ARRAY_SIZE(two_length_country_code_map) &&
     71             two_length_country_code_map[ccc_candidate];
     72 }
     73 
     74 /**
     75  * Returns interger corresponding to the input if input "ch" is
     76  * ISO-LATIN characters 0-9.
     77  * Returns -1 otherwise
     78  */
     79 static int tryGetISODigit (char ch)
     80 {
     81     if ('0' <= ch && ch <= '9') {
     82         return ch - '0';
     83     } else {
     84         return -1;
     85     }
     86 }
     87 
     88 /**
     89  * True if ch is ISO-LATIN characters 0-9, *, # , +
     90  * Note this method current does not account for the WILD char 'N'
     91  */
     92 static bool isDialable(char ch)
     93 {
     94     return ('0' <= ch && ch <= '9') || ch == '*' || ch == '#' || ch == '+';
     95 }
     96 
     97 /** Returns true if ch is not dialable or alpha char */
     98 static bool isSeparator(char ch)
     99 {
    100     return !isDialable(ch) && (isalpha(ch) == 0);
    101 }
    102 
    103 /**
    104  * Try to store the pointer to "new_ptr" which does not have trunk prefix.
    105  *
    106  * Currently this function simply ignore the first digit assuming it is
    107  * trunk prefix. Actually trunk prefix is different in each country.
    108  *
    109  * e.g.
    110  * "+79161234567" equals "89161234567" (Russian trunk digit is 8)
    111  * "+33123456789" equals "0123456789" (French trunk digit is 0)
    112  *
    113  */
    114 static bool tryGetTrunkPrefixOmittedStr(const char *str, size_t len,
    115                                         const char **new_ptr, size_t *new_len)
    116 {
    117     for (size_t i = 0 ; i < len ; i++) {
    118         char ch = str[i];
    119         if (tryGetISODigit(ch) >= 0) {
    120             if (new_ptr != NULL) {
    121                 *new_ptr = str + i + 1;
    122             }
    123             if (new_len != NULL) {
    124                 *new_len = len - (i + 1);
    125             }
    126             return true;
    127         } else if (isDialable(ch)) {
    128             return false;
    129         }
    130     }
    131 
    132     return false;
    133 }
    134 
    135 /*
    136  * Note that this function does not strictly care the country calling code with
    137  * 3 length (like Morocco: +212), assuming it is enough to use the first two
    138  * digit to compare two phone numbers.
    139  */
    140 static int tryGetCountryCallingCode(const char *str, size_t len,
    141                                     const char **new_ptr, size_t *new_len,
    142                                     bool accept_thailand_case)
    143 {
    144     // Rough regexp:
    145     //  ^[^0-9*#+]*((\+|0(0|11)\d\d?|166) [^0-9*#+] $
    146     //         0        1 2 3 45  6 7  89
    147     //
    148     // In all the states, this function ignores separator characters.
    149     // "166" is the special case for the call from Thailand to the US. Ugu!
    150 
    151     int state = 0;
    152     int ccc = 0;
    153     for (size_t i = 0 ; i < len ; i++ ) {
    154         char ch = str[i];
    155         switch (state) {
    156             case 0:
    157                 if      (ch == '+') state = 1;
    158                 else if (ch == '0') state = 2;
    159                 else if (ch == '1') {
    160                     if (accept_thailand_case) {
    161                         state = 8;
    162                     } else {
    163                         return -1;
    164                     }
    165                 } else if (isDialable(ch)) return -1;
    166             break;
    167 
    168             case 2:
    169                 if      (ch == '0') state = 3;
    170                 else if (ch == '1') state = 4;
    171                 else if (isDialable(ch)) return -1;
    172             break;
    173 
    174             case 4:
    175                 if      (ch == '1') state = 5;
    176                 else if (isDialable(ch)) return -1;
    177             break;
    178 
    179             case 1:
    180             case 3:
    181             case 5:
    182             case 6:
    183             case 7:
    184                 {
    185                     int ret = tryGetISODigit(ch);
    186                     if (ret > 0) {
    187                         ccc = ccc * 10 + ret;
    188                         if (ccc >= 100 || isCountryCallingCode(ccc)) {
    189                             if (new_ptr != NULL) {
    190                                 *new_ptr = str + i + 1;
    191                             }
    192                             if (new_len != NULL) {
    193                                 *new_len = len - (i + 1);
    194                             }
    195                             return ccc;
    196                         }
    197                         if (state == 1 || state == 3 || state == 5) {
    198                             state = 6;
    199                         } else {
    200                             state++;
    201                         }
    202                     } else if (isDialable(ch)) {
    203                         return -1;
    204                     }
    205                 }
    206                 break;
    207             case 8:
    208                 if (ch == '6') state = 9;
    209                 else if (isDialable(ch)) return -1;
    210                 break;
    211             case 9:
    212                 if (ch == '6') {
    213                     if (new_ptr != NULL) {
    214                         *new_ptr = str + i + 1;
    215                     }
    216                     if (new_len != NULL) {
    217                         *new_len = len - (i + 1);
    218                     }
    219                     return 66;
    220                 } else {
    221                     return -1;
    222                 }
    223                 break;
    224             default:
    225                 return -1;
    226         }
    227     }
    228 
    229     return -1;
    230 }
    231 
    232 /**
    233  * Return true if the prefix of "ch" is "ignorable". Here, "ignorable" means
    234  * that "ch" has only one digit and separator characters. The one digit is
    235  * assumed to be the trunk prefix.
    236  */
    237 static bool checkPrefixIsIgnorable(const char* ch, int i) {
    238     bool trunk_prefix_was_read = false;
    239     while (i >= 0) {
    240         if (tryGetISODigit(ch[i]) >= 0) {
    241             if (trunk_prefix_was_read) {
    242                 // More than one digit appeared, meaning that "a" and "b"
    243                 // is different.
    244                 return false;
    245             } else {
    246                 // Ignore just one digit, assuming it is trunk prefix.
    247                 trunk_prefix_was_read = true;
    248             }
    249         } else if (isDialable(ch[i])) {
    250             // Trunk prefix is a digit, not "*", "#"...
    251             return false;
    252         }
    253         i--;
    254     }
    255 
    256     return true;
    257 }
    258 
    259 /**
    260  * Compare phone numbers a and b, return true if they're identical
    261  * enough for caller ID purposes.
    262  *
    263  * Assume NULL as 0-length string.
    264  *
    265  * Detailed information:
    266  * Currently (as of 2009-06-12), we cannot depend on the locale given from the
    267  * OS. For example, current Android does not accept "en_JP", meaning
    268  * "the display language is English but the phone should be in Japan", but
    269  * en_US, es_US, etc. So we cannot identify which digit is valid trunk prefix
    270  * in the country where the phone is used. More specifically, "880-1234-1234"
    271  * is not valid phone number in Japan since the trunk prefix in Japan is not 8
    272  * but 0 (correct number should be "080-1234-1234"), while Russian trunk prefix
    273  * is 8. Also, we cannot know whether the country where users live has trunk
    274  * prefix itself. So, we cannot determine whether "+81-80-1234-1234" is NOT
    275  * same as "880-1234-1234" (while "+81-80-1234-1234" is same as "080-1234-1234"
    276  * and we can determine "880-1234-1234" is different from "080-1234-1234").
    277  *
    278  * In the future, we should handle trunk prefix more correctly, but as of now,
    279  * we just ignore it...
    280  */
    281 static bool phone_number_compare_inter(const char* const org_a, const char* const org_b,
    282                                        bool accept_thailand_case)
    283 {
    284     const char* a = org_a;
    285     const char* b = org_b;
    286     size_t len_a = 0;
    287     size_t len_b = 0;
    288     if (a == NULL) {
    289         a = "";
    290     } else {
    291         len_a = strlen(a);
    292     }
    293     if (b == NULL) {
    294         b = "";
    295     } else {
    296         len_b = strlen(b);
    297     }
    298 
    299     const char* tmp_a = NULL;
    300     const char* tmp_b = NULL;
    301     size_t tmp_len_a = len_a;
    302     size_t tmp_len_b = len_b;
    303 
    304     int ccc_a = tryGetCountryCallingCode(a, len_a, &tmp_a, &tmp_len_a, accept_thailand_case);
    305     int ccc_b = tryGetCountryCallingCode(b, len_b, &tmp_b, &tmp_len_b, accept_thailand_case);
    306     bool both_have_ccc = false;
    307     bool may_ignore_prefix = true;
    308     bool trunk_prefix_is_omitted_a = false;
    309     bool trunk_prefix_is_omitted_b = false;
    310     if (ccc_a >= 0 && ccc_b >= 0) {
    311         if (ccc_a != ccc_b) {
    312             // Different Country Calling Code. Must be different phone number.
    313             return false;
    314         }
    315         // When both have ccc, do not ignore trunk prefix. Without this,
    316         // "+81123123" becomes same as "+810123123" (+81 == Japan)
    317         may_ignore_prefix = false;
    318         both_have_ccc = true;
    319     } else if (ccc_a < 0 && ccc_b < 0) {
    320         // When both do not have ccc, do not ignore trunk prefix. Without this,
    321         // "123123" becomes same as "0123123"
    322         may_ignore_prefix = false;
    323     } else {
    324         if (ccc_a < 0) {
    325             tryGetTrunkPrefixOmittedStr(a, len_a, &tmp_a, &tmp_len_a);
    326             trunk_prefix_is_omitted_a = true;
    327         }
    328         if (ccc_b < 0) {
    329             tryGetTrunkPrefixOmittedStr(b, len_b, &tmp_b, &tmp_len_b);
    330             trunk_prefix_is_omitted_b = true;
    331         }
    332     }
    333 
    334     if (tmp_a != NULL) {
    335         a = tmp_a;
    336         len_a = tmp_len_a;
    337     }
    338     if (tmp_b != NULL) {
    339         b = tmp_b;
    340         len_b = tmp_len_b;
    341     }
    342 
    343     int i_a = len_a - 1;
    344     int i_b = len_b - 1;
    345     while (i_a >= 0 && i_b >= 0) {
    346         bool skip_compare = false;
    347         char ch_a = a[i_a];
    348         char ch_b = b[i_b];
    349         if (isSeparator(ch_a)) {
    350             i_a--;
    351             skip_compare = true;
    352         }
    353         if (isSeparator(ch_b)) {
    354             i_b--;
    355             skip_compare = true;
    356         }
    357 
    358         if (!skip_compare) {
    359             if (ch_a != ch_b) {
    360                 return false;
    361             }
    362             i_a--;
    363             i_b--;
    364         }
    365     }
    366 
    367     if (may_ignore_prefix) {
    368         bool trunk_prefix_ignorable_a = checkPrefixIsIgnorable(a, i_a);
    369         if ((trunk_prefix_is_omitted_a && i_a >= 0) || !trunk_prefix_ignorable_a) {
    370             if (accept_thailand_case) {
    371                 // Maybe the code handling the special case for Thailand makes the
    372                 // result garbled, so disable the code and try again.
    373                 // e.g. "16610001234" must equal to "6610001234", but with
    374                 //      Thailand-case handling code, they become equal to each other.
    375                 //
    376                 // Note: we select simplicity rather than adding some complicated
    377                 //       logic here for performance(like "checking whether remaining
    378                 //       numbers are just 66 or not"), assuming inputs are small
    379                 //       enough.
    380                 return phone_number_compare_inter(org_a, org_b, false);
    381             } else {
    382                 return false;
    383             }
    384         } else if (trunk_prefix_ignorable_a && trunk_prefix_is_omitted_b) {
    385             bool cmp_prefixes = i_a == 0 && isDialable(a[i_a]);
    386             if (cmp_prefixes && org_b[i_a] != a[i_a]) {
    387                 // Unmatched trunk prefix
    388                 return false;
    389             }
    390         }
    391 
    392         bool trunk_prefix_ignorable_b = checkPrefixIsIgnorable(b, i_b);
    393         if ((trunk_prefix_is_omitted_b && i_b >= 0) || !trunk_prefix_ignorable_b) {
    394             if (accept_thailand_case) {
    395                 return phone_number_compare_inter(org_a, org_b, false);
    396             } else {
    397                 return false;
    398             }
    399         } else if (trunk_prefix_ignorable_b && trunk_prefix_is_omitted_a) {
    400             bool cmp_prefixes = i_b == 0 && isDialable(b[i_b]);
    401             if (cmp_prefixes && org_a[i_b] != b[i_b]) {
    402                 // Unmatched trunk prefix
    403                 return false;
    404             }
    405         }
    406     } else {
    407         // In the US, 1-650-555-1234 must be equal to 650-555-1234,
    408         // while 090-1234-1234 must not be equal to 90-1234-1234 in Japan.
    409         // This request exists just in US (with 1 trunk (NDD) prefix).
    410         // In addition, "011 11 7005554141" must not equal to "+17005554141",
    411         // while "011 1 7005554141" must equal to "+17005554141"
    412         //
    413         // In this comparison, we ignore the prefix '1' just once, when
    414         // - at least either does not have CCC, or
    415         // - the remaining non-separator number is 1
    416         bool may_be_namp = !both_have_ccc;
    417         while (i_a >= 0) {
    418             const char ch_a = a[i_a];
    419             if (isDialable(ch_a)) {
    420                 if (may_be_namp && tryGetISODigit(ch_a) == 1) {
    421                     may_be_namp = false;
    422                 } else {
    423                     return false;
    424                 }
    425             }
    426             i_a--;
    427         }
    428         while (i_b >= 0) {
    429             const char ch_b = b[i_b];
    430             if (isDialable(ch_b)) {
    431                 if (may_be_namp && tryGetISODigit(ch_b) == 1) {
    432                     may_be_namp = false;
    433                 } else {
    434                     return false;
    435                 }
    436             }
    437             i_b--;
    438         }
    439     }
    440 
    441     return true;
    442 }
    443 
    444 bool phone_number_compare_strict(const char* a, const char* b)
    445 {
    446     return phone_number_compare_inter(a, b, true);
    447 }
    448 
    449 /**
    450  * Imitates the Java method PhoneNumberUtils.getStrippedReversed.
    451  * Used for API compatibility with Android 1.6 and earlier.
    452  */
    453 bool phone_number_stripped_reversed_inter(const char* in, char* out, const int len, int *outlen) {
    454     int in_len = strlen(in);
    455     int out_len = 0;
    456     bool have_seen_plus = false;
    457     for (int i = in_len; --i >= 0;) {
    458         char c = in[i];
    459         if ((c >= '0' && c <= '9') || c == '*' || c == '#' || c == 'N') {
    460             if (out_len < len) {
    461                 out[out_len++] = c;
    462             }
    463         } else {
    464             switch (c) {
    465               case '+':
    466                   if (!have_seen_plus) {
    467                       if (out_len < len) {
    468                           out[out_len++] = c;
    469                       }
    470                       have_seen_plus = true;
    471                   }
    472                   break;
    473               case ',':
    474               case ';':
    475                   out_len = 0;
    476                   break;
    477           }
    478         }
    479     }
    480 
    481     *outlen = out_len;
    482     return true;
    483 }
    484 
    485 }  // namespace android
    486