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 20namespace 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 26import sys 27ccc_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 33ONE_LINE_NUM = 10 34 35for 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*/ 48static 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 */ 68static 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 */ 79static 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 */ 92static 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 */ 98static 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 */ 114static 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 */ 140static 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 */ 237static 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 */ 281static 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 444bool 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 */ 453bool 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