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 */
isCountryCallingCode(int ccc_candidate)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 */
tryGetISODigit(char ch)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 */
isDialable(char ch)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 */
isSeparator(char ch)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 */
tryGetTrunkPrefixOmittedStr(const char * str,size_t len,const char ** new_ptr,size_t * new_len)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 */
tryGetCountryCallingCode(const char * str,size_t len,const char ** new_ptr,size_t * new_len)140 static int tryGetCountryCallingCode(const char *str, size_t len,
141 const char **new_ptr, size_t *new_len)
142 {
143 // Rough regexp:
144 // ^[^0-9*#+]*((\+|0(0|11)\d\d?|166) [^0-9*#+] $
145 // 0 1 2 3 45 6 7 89
146 //
147 // In all the states, this function ignores separator characters.
148 // "166" is the special case for the call from Thailand to the US. Ugu!
149
150 int state = 0;
151 int ccc = 0;
152 for (size_t i = 0 ; i < len ; i++ ) {
153 char ch = str[i];
154 switch (state) {
155 case 0:
156 if (ch == '+') state = 1;
157 else if (ch == '0') state = 2;
158 else if (ch == '1') state = 8;
159 else if (isDialable(ch)) return -1;
160 break;
161
162 case 2:
163 if (ch == '0') state = 3;
164 else if (ch == '1') state = 4;
165 else if (isDialable(ch)) return -1;
166 break;
167
168 case 4:
169 if (ch == '1') state = 5;
170 else if (isDialable(ch)) return -1;
171 break;
172
173 case 1:
174 case 3:
175 case 5:
176 case 6:
177 case 7:
178 {
179 int ret = tryGetISODigit(ch);
180 if (ret > 0) {
181 ccc = ccc * 10 + ret;
182 if (ccc >= 100 || isCountryCallingCode(ccc)) {
183 if (new_ptr != NULL) {
184 *new_ptr = str + i + 1;
185 }
186 if (new_len != NULL) {
187 *new_len = len - (i + 1);
188 }
189 return ccc;
190 }
191 if (state == 1 || state == 3 || state == 5) {
192 state = 6;
193 } else {
194 state++;
195 }
196 } else if (isDialable(ch)) {
197 return -1;
198 }
199 }
200 break;
201 case 8:
202 if (ch == '6') state = 9;
203 else if (isDialable(ch)) return -1;
204 break;
205 case 9:
206 if (ch == '6') {
207 if (new_ptr != NULL) {
208 *new_ptr = str + i + 1;
209 }
210 if (new_len != NULL) {
211 *new_len = len - (i + 1);
212 }
213 return 66;
214 } else {
215 return -1;
216 }
217 break;
218 default:
219 return -1;
220 }
221 }
222
223 return -1;
224 }
225
226 /**
227 * Return true if the prefix of "ch" is "ignorable". Here, "ignorable" means
228 * that "ch" has only one digit and separater characters. The one digit is
229 * assumed to be trunk prefix.
230 */
checkPrefixIsIgnorable(const char * ch,int i)231 static bool checkPrefixIsIgnorable(const char* ch, int i) {
232 bool trunk_prefix_was_read = false;
233 while (i >= 0) {
234 if (tryGetISODigit(ch[i]) >= 0) {
235 if (trunk_prefix_was_read) {
236 // More than one digit appeared, meaning that "a" and "b"
237 // is different.
238 return false;
239 } else {
240 // Ignore just one digit, assuming it is trunk prefix.
241 trunk_prefix_was_read = true;
242 }
243 } else if (isDialable(ch[i])) {
244 // Trunk prefix is a digit, not "*", "#"...
245 return false;
246 }
247 i--;
248 }
249
250 return true;
251 }
252
253 /**
254 * Compare phone numbers a and b, return true if they're identical
255 * enough for caller ID purposes.
256 *
257 * Assume NULL as 0-length string.
258 *
259 * Detailed information:
260 * Currently (as of 2009-06-12), we cannot depend on the locale given from the
261 * OS. For example, current Android does not accept "en_JP", meaning
262 * "the display language is English but the phone should be in Japan", but
263 * en_US, es_US, etc. So we cannot identify which digit is valid trunk prefix
264 * in the country where the phone is used. More specifically, "880-1234-1234"
265 * is not valid phone number in Japan since the trunk prefix in Japan is not 8
266 * but 0 (correct number should be "080-1234-1234"), while Russian trunk prefix
267 * is 8. Also, we cannot know whether the country where users live has trunk
268 * prefix itself. So, we cannot determine whether "+81-80-1234-1234" is NOT
269 * same as "880-1234-1234" (while "+81-80-1234-1234" is same as "080-1234-1234"
270 * and we can determine "880-1234-1234" is different from "080-1234-1234").
271 *
272 * In the future, we should handle trunk prefix more correctly, but as of now,
273 * we just ignore it...
274 */
phone_number_compare(const char * a,const char * b)275 bool phone_number_compare(const char* a, const char* b)
276 {
277 size_t len_a = 0;
278 size_t len_b = 0;
279 if (a == NULL) {
280 a = "";
281 } else {
282 len_a = strlen(a);
283 }
284 if (b == NULL) {
285 b = "";
286 } else {
287 len_b = strlen(b);
288 }
289
290 const char* tmp_a = NULL;
291 const char* tmp_b = NULL;
292 size_t tmp_len_a = len_a;
293 size_t tmp_len_b = len_b;
294
295 int ccc_a = tryGetCountryCallingCode(a, len_a, &tmp_a, &tmp_len_a);
296 int ccc_b = tryGetCountryCallingCode(b, len_b, &tmp_b, &tmp_len_b);
297 bool ok_to_ignore_prefix = true;
298 if (ccc_a >= 0 && ccc_b >= 0) {
299 if (ccc_a != ccc_b) {
300 // Different Country Calling Code. Must be different phone number.
301 return false;
302 }
303 // When both have ccc, do not ignore trunk prefix. Without this,
304 // "+81123123" becomes same as "+810123123" (+81 == Japan)
305 ok_to_ignore_prefix = false;
306 } else if (ccc_a < 0 && ccc_b < 0) {
307 // When both do not have ccc, do not ignore trunk prefix. Without this,
308 // "123123" becomes same as "0123123"
309 ok_to_ignore_prefix = false;
310 } else {
311 if (ccc_a < 0) {
312 tryGetTrunkPrefixOmittedStr(a, len_a, &tmp_a, &tmp_len_a);
313 }
314 if (ccc_b < 0) {
315 tryGetTrunkPrefixOmittedStr(b, len_b, &tmp_b, &tmp_len_b);
316 }
317 }
318
319 if (tmp_a != NULL) {
320 a = tmp_a;
321 len_a = tmp_len_a;
322 }
323 if (tmp_b != NULL) {
324 b = tmp_b;
325 len_b = tmp_len_b;
326 }
327
328 int i_a = len_a - 1;
329 int i_b = len_b - 1;
330 while (i_a >= 0 && i_b >= 0) {
331 bool skip_compare = false;
332 char ch_a = a[i_a];
333 char ch_b = b[i_b];
334 if (isSeparator(ch_a)) {
335 i_a--;
336 skip_compare = true;
337 }
338 if (isSeparator(ch_b)) {
339 i_b--;
340 skip_compare = true;
341 }
342
343 if (!skip_compare) {
344 if (ch_a != ch_b) {
345 return false;
346 }
347 i_a--;
348 i_b--;
349 }
350 }
351
352 if (ok_to_ignore_prefix) {
353 if (!checkPrefixIsIgnorable(a, i_a)) {
354 return false;
355 }
356 if (!checkPrefixIsIgnorable(b, i_b)) {
357 return false;
358 }
359 } else {
360 // In the US, 1-650-555-1234 must be equal to 650-555-1234,
361 // while 090-1234-1234 must not be equalt to 90-1234-1234 in Japan.
362 // This request exists just in US (with 1 trunk (NDD) prefix).
363 //
364 // At least, in this "rough" comparison, we should ignore the prefix
365 // '1', so if the remaining non-separator number is 0, we ignore it
366 // just once.
367 bool may_be_namp = true;
368 while (i_a >= 0) {
369 const char ch_a = a[i_a];
370 if (isDialable(ch_a)) {
371 if (may_be_namp && tryGetISODigit(ch_a) == 1) {
372 may_be_namp = false;
373 } else {
374 return false;
375 }
376 }
377 i_a--;
378 }
379 while (i_b >= 0) {
380 const char ch_b = b[i_b];
381 if (isDialable(ch_b)) {
382 if (may_be_namp && tryGetISODigit(ch_b) == 1) {
383 may_be_namp = false;
384 } else {
385 return false;
386 }
387 }
388 i_b--;
389 }
390 }
391
392 return true;
393 }
394
395 } // namespace android
396