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 separater characters. The one digit is 235 * assumed to be 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 ok_to_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 ok_to_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 ok_to_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 (ok_to_ignore_prefix) { 368 if ((trunk_prefix_is_omitted_a && i_a >= 0) || 369 !checkPrefixIsIgnorable(a, i_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 } 385 if ((trunk_prefix_is_omitted_b && i_b >= 0) || 386 !checkPrefixIsIgnorable(b, i_b)) { 387 if (accept_thailand_case) { 388 return phone_number_compare_inter(org_a, org_b, false); 389 } else { 390 return false; 391 } 392 } 393 } else { 394 // In the US, 1-650-555-1234 must be equal to 650-555-1234, 395 // while 090-1234-1234 must not be equalt to 90-1234-1234 in Japan. 396 // This request exists just in US (with 1 trunk (NDD) prefix). 397 // In addition, "011 11 7005554141" must not equal to "+17005554141", 398 // while "011 1 7005554141" must equal to "+17005554141" 399 // 400 // In this comparison, we ignore the prefix '1' just once, when 401 // - at least either does not have CCC, or 402 // - the remaining non-separator number is 1 403 bool may_be_namp = !both_have_ccc; 404 while (i_a >= 0) { 405 const char ch_a = a[i_a]; 406 if (isDialable(ch_a)) { 407 if (may_be_namp && tryGetISODigit(ch_a) == 1) { 408 may_be_namp = false; 409 } else { 410 return false; 411 } 412 } 413 i_a--; 414 } 415 while (i_b >= 0) { 416 const char ch_b = b[i_b]; 417 if (isDialable(ch_b)) { 418 if (may_be_namp && tryGetISODigit(ch_b) == 1) { 419 may_be_namp = false; 420 } else { 421 return false; 422 } 423 } 424 i_b--; 425 } 426 } 427 428 return true; 429 } 430 431 bool phone_number_compare_strict(const char* a, const char* b) 432 { 433 return phone_number_compare_inter(a, b, true); 434 } 435 436 /** 437 * Imitates the Java method PhoneNumberUtils.getStrippedReversed. 438 * Used for API compatibility with Android 1.6 and earlier. 439 */ 440 bool phone_number_stripped_reversed_inter(const char* in, char* out, const int len, int *outlen) { 441 int in_len = strlen(in); 442 int out_len = 0; 443 bool have_seen_plus = false; 444 for (int i = in_len; --i >= 0;) { 445 char c = in[i]; 446 if ((c >= '0' && c <= '9') || c == '*' || c == '#' || c == 'N') { 447 if (out_len < len) { 448 out[out_len++] = c; 449 } 450 } else { 451 switch (c) { 452 case '+': 453 if (!have_seen_plus) { 454 if (out_len < len) { 455 out[out_len++] = c; 456 } 457 have_seen_plus = true; 458 } 459 break; 460 case ',': 461 case ';': 462 out_len = 0; 463 break; 464 } 465 } 466 } 467 468 *outlen = out_len; 469 return true; 470 } 471 472 } // namespace android 473