• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006 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 package android.telephony;
18 
19 import com.android.i18n.phonenumbers.NumberParseException;
20 import com.android.i18n.phonenumbers.PhoneNumberUtil;
21 import com.android.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
22 import com.android.i18n.phonenumbers.Phonenumber.PhoneNumber;
23 
24 import android.content.Context;
25 import android.content.Intent;
26 import android.database.Cursor;
27 import android.location.CountryDetector;
28 import android.net.Uri;
29 import android.os.SystemProperties;
30 import android.provider.Contacts;
31 import android.provider.ContactsContract;
32 import android.text.Editable;
33 import android.text.SpannableStringBuilder;
34 import android.text.TextUtils;
35 import android.util.Log;
36 import android.util.SparseIntArray;
37 
38 import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_ISO_COUNTRY;
39 import static com.android.internal.telephony.TelephonyProperties.PROPERTY_IDP_STRING;
40 import static com.android.internal.telephony.TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY;
41 
42 import java.util.Locale;
43 import java.util.regex.Matcher;
44 import java.util.regex.Pattern;
45 
46 /**
47  * Various utilities for dealing with phone number strings.
48  */
49 public class PhoneNumberUtils
50 {
51     /*
52      * Special characters
53      *
54      * (See "What is a phone number?" doc)
55      * 'p' --- GSM pause character, same as comma
56      * 'n' --- GSM wild character
57      * 'w' --- GSM wait character
58      */
59     public static final char PAUSE = ',';
60     public static final char WAIT = ';';
61     public static final char WILD = 'N';
62 
63     /*
64      * Calling Line Identification Restriction (CLIR)
65      */
66     private static final String CLIR_ON = "*31#+";
67     private static final String CLIR_OFF = "#31#+";
68 
69     /*
70      * TOA = TON + NPI
71      * See TS 24.008 section 10.5.4.7 for details.
72      * These are the only really useful TOA values
73      */
74     public static final int TOA_International = 0x91;
75     public static final int TOA_Unknown = 0x81;
76 
77     static final String LOG_TAG = "PhoneNumberUtils";
78     private static final boolean DBG = false;
79 
80     /*
81      * global-phone-number = ["+"] 1*( DIGIT / written-sep )
82      * written-sep         = ("-"/".")
83      */
84     private static final Pattern GLOBAL_PHONE_NUMBER_PATTERN =
85             Pattern.compile("[\\+]?[0-9.-]+");
86 
87     /** True if c is ISO-LATIN characters 0-9 */
88     public static boolean
isISODigit(char c)89     isISODigit (char c) {
90         return c >= '0' && c <= '9';
91     }
92 
93     /** True if c is ISO-LATIN characters 0-9, *, # */
94     public final static boolean
is12Key(char c)95     is12Key(char c) {
96         return (c >= '0' && c <= '9') || c == '*' || c == '#';
97     }
98 
99     /** True if c is ISO-LATIN characters 0-9, *, # , +, WILD  */
100     public final static boolean
isDialable(char c)101     isDialable(char c) {
102         return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+' || c == WILD;
103     }
104 
105     /** True if c is ISO-LATIN characters 0-9, *, # , + (no WILD)  */
106     public final static boolean
isReallyDialable(char c)107     isReallyDialable(char c) {
108         return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+';
109     }
110 
111     /** True if c is ISO-LATIN characters 0-9, *, # , +, WILD, WAIT, PAUSE   */
112     public final static boolean
isNonSeparator(char c)113     isNonSeparator(char c) {
114         return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+'
115                 || c == WILD || c == WAIT || c == PAUSE;
116     }
117 
118     /** This any anything to the right of this char is part of the
119      *  post-dial string (eg this is PAUSE or WAIT)
120      */
121     public final static boolean
isStartsPostDial(char c)122     isStartsPostDial (char c) {
123         return c == PAUSE || c == WAIT;
124     }
125 
126     private static boolean
isPause(char c)127     isPause (char c){
128         return c == 'p'||c == 'P';
129     }
130 
131     private static boolean
isToneWait(char c)132     isToneWait (char c){
133         return c == 'w'||c == 'W';
134     }
135 
136 
137     /** Returns true if ch is not dialable or alpha char */
isSeparator(char ch)138     private static boolean isSeparator(char ch) {
139         return !isDialable(ch) && !(('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z'));
140     }
141 
142     /** Extracts the phone number from an Intent.
143      *
144      * @param intent the intent to get the number of
145      * @param context a context to use for database access
146      *
147      * @return the phone number that would be called by the intent, or
148      *         <code>null</code> if the number cannot be found.
149      */
getNumberFromIntent(Intent intent, Context context)150     public static String getNumberFromIntent(Intent intent, Context context) {
151         String number = null;
152 
153         Uri uri = intent.getData();
154         String scheme = uri.getScheme();
155 
156         if (scheme.equals("tel") || scheme.equals("sip")) {
157             return uri.getSchemeSpecificPart();
158         }
159 
160         // TODO: We don't check for SecurityException here (requires
161         // CALL_PRIVILEGED permission).
162         if (scheme.equals("voicemail")) {
163             return TelephonyManager.getDefault().getCompleteVoiceMailNumber();
164         }
165 
166         if (context == null) {
167             return null;
168         }
169 
170         String type = intent.resolveType(context);
171         String phoneColumn = null;
172 
173         // Correctly read out the phone entry based on requested provider
174         final String authority = uri.getAuthority();
175         if (Contacts.AUTHORITY.equals(authority)) {
176             phoneColumn = Contacts.People.Phones.NUMBER;
177         } else if (ContactsContract.AUTHORITY.equals(authority)) {
178             phoneColumn = ContactsContract.CommonDataKinds.Phone.NUMBER;
179         }
180 
181         final Cursor c = context.getContentResolver().query(uri, new String[] {
182             phoneColumn
183         }, null, null, null);
184         if (c != null) {
185             try {
186                 if (c.moveToFirst()) {
187                     number = c.getString(c.getColumnIndex(phoneColumn));
188                 }
189             } finally {
190                 c.close();
191             }
192         }
193 
194         return number;
195     }
196 
197     /** Extracts the network address portion and canonicalizes
198      *  (filters out separators.)
199      *  Network address portion is everything up to DTMF control digit
200      *  separators (pause or wait), but without non-dialable characters.
201      *
202      *  Please note that the GSM wild character is allowed in the result.
203      *  This must be resolved before dialing.
204      *
205      *  Returns null if phoneNumber == null
206      */
207     public static String
extractNetworkPortion(String phoneNumber)208     extractNetworkPortion(String phoneNumber) {
209         if (phoneNumber == null) {
210             return null;
211         }
212 
213         int len = phoneNumber.length();
214         StringBuilder ret = new StringBuilder(len);
215         boolean firstCharAdded = false;
216 
217         for (int i = 0; i < len; i++) {
218             char c = phoneNumber.charAt(i);
219             if (isDialable(c) && (c != '+' || !firstCharAdded)) {
220                 firstCharAdded = true;
221                 ret.append(c);
222             } else if (isStartsPostDial (c)) {
223                 break;
224             }
225         }
226 
227         int pos = addPlusChar(phoneNumber);
228         if (pos >= 0 && ret.length() > pos) {
229             ret.insert(pos, '+');
230         }
231 
232         return ret.toString();
233     }
234 
235     /**
236      * Extracts the network address portion and canonicalize.
237      *
238      * This function is equivalent to extractNetworkPortion(), except
239      * for allowing the PLUS character to occur at arbitrary positions
240      * in the address portion, not just the first position.
241      *
242      * @hide
243      */
extractNetworkPortionAlt(String phoneNumber)244     public static String extractNetworkPortionAlt(String phoneNumber) {
245         if (phoneNumber == null) {
246             return null;
247         }
248 
249         int len = phoneNumber.length();
250         StringBuilder ret = new StringBuilder(len);
251         boolean haveSeenPlus = false;
252 
253         for (int i = 0; i < len; i++) {
254             char c = phoneNumber.charAt(i);
255             if (c == '+') {
256                 if (haveSeenPlus) {
257                     continue;
258                 }
259                 haveSeenPlus = true;
260             }
261             if (isDialable(c)) {
262                 ret.append(c);
263             } else if (isStartsPostDial (c)) {
264                 break;
265             }
266         }
267 
268         return ret.toString();
269     }
270 
271     /**
272      * Strips separators from a phone number string.
273      * @param phoneNumber phone number to strip.
274      * @return phone string stripped of separators.
275      */
stripSeparators(String phoneNumber)276     public static String stripSeparators(String phoneNumber) {
277         if (phoneNumber == null) {
278             return null;
279         }
280         int len = phoneNumber.length();
281         StringBuilder ret = new StringBuilder(len);
282 
283         for (int i = 0; i < len; i++) {
284             char c = phoneNumber.charAt(i);
285             if (isNonSeparator(c)) {
286                 ret.append(c);
287             }
288         }
289 
290         return ret.toString();
291     }
292 
293     /**
294      * Converts pause and tonewait pause characters
295      * to Android representation.
296      * RFC 3601 says pause is 'p' and tonewait is 'w'.
297      * @hide
298      */
convertPreDial(String phoneNumber)299     public static String convertPreDial(String phoneNumber) {
300         if (phoneNumber == null) {
301             return null;
302         }
303         int len = phoneNumber.length();
304         StringBuilder ret = new StringBuilder(len);
305 
306         for (int i = 0; i < len; i++) {
307             char c = phoneNumber.charAt(i);
308 
309             if (isPause(c)) {
310                 c = PAUSE;
311             } else if (isToneWait(c)) {
312                 c = WAIT;
313             }
314             ret.append(c);
315         }
316         return ret.toString();
317     }
318 
319     /** or -1 if both are negative */
320     static private int
minPositive(int a, int b)321     minPositive (int a, int b) {
322         if (a >= 0 && b >= 0) {
323             return (a < b) ? a : b;
324         } else if (a >= 0) { /* && b < 0 */
325             return a;
326         } else if (b >= 0) { /* && a < 0 */
327             return b;
328         } else { /* a < 0 && b < 0 */
329             return -1;
330         }
331     }
332 
log(String msg)333     private static void log(String msg) {
334         Log.d(LOG_TAG, msg);
335     }
336     /** index of the last character of the network portion
337      *  (eg anything after is a post-dial string)
338      */
339     static private int
indexOfLastNetworkChar(String a)340     indexOfLastNetworkChar(String a) {
341         int pIndex, wIndex;
342         int origLength;
343         int trimIndex;
344 
345         origLength = a.length();
346 
347         pIndex = a.indexOf(PAUSE);
348         wIndex = a.indexOf(WAIT);
349 
350         trimIndex = minPositive(pIndex, wIndex);
351 
352         if (trimIndex < 0) {
353             return origLength - 1;
354         } else {
355             return trimIndex - 1;
356         }
357     }
358 
359     /** GSM codes
360      *  Finds if a GSM code includes the international prefix (+).
361      *
362      * @param number the number to dial.
363      *
364      * @return the position where the + char will be inserted, -1 if the GSM code was not found.
365      */
366     private static int
addPlusChar(String number)367     addPlusChar(String number) {
368         int pos = -1;
369 
370         if (number.startsWith(CLIR_OFF)) {
371             pos = CLIR_OFF.length() - 1;
372         }
373 
374         if (number.startsWith(CLIR_ON)) {
375             pos = CLIR_ON.length() - 1;
376         }
377 
378         return pos;
379     }
380 
381     /**
382      * Extracts the post-dial sequence of DTMF control digits, pauses, and
383      * waits. Strips separators. This string may be empty, but will not be null
384      * unless phoneNumber == null.
385      *
386      * Returns null if phoneNumber == null
387      */
388 
389     public static String
extractPostDialPortion(String phoneNumber)390     extractPostDialPortion(String phoneNumber) {
391         if (phoneNumber == null) return null;
392 
393         int trimIndex;
394         StringBuilder ret = new StringBuilder();
395 
396         trimIndex = indexOfLastNetworkChar (phoneNumber);
397 
398         for (int i = trimIndex + 1, s = phoneNumber.length()
399                 ; i < s; i++
400         ) {
401             char c = phoneNumber.charAt(i);
402             if (isNonSeparator(c)) {
403                 ret.append(c);
404             }
405         }
406 
407         return ret.toString();
408     }
409 
410     /**
411      * Compare phone numbers a and b, return true if they're identical enough for caller ID purposes.
412      */
compare(String a, String b)413     public static boolean compare(String a, String b) {
414         // We've used loose comparation at least Eclair, which may change in the future.
415 
416         return compare(a, b, false);
417     }
418 
419     /**
420      * Compare phone numbers a and b, and return true if they're identical
421      * enough for caller ID purposes. Checks a resource to determine whether
422      * to use a strict or loose comparison algorithm.
423      */
compare(Context context, String a, String b)424     public static boolean compare(Context context, String a, String b) {
425         boolean useStrict = context.getResources().getBoolean(
426                com.android.internal.R.bool.config_use_strict_phone_number_comparation);
427         return compare(a, b, useStrict);
428     }
429 
430     /**
431      * @hide only for testing.
432      */
compare(String a, String b, boolean useStrictComparation)433     public static boolean compare(String a, String b, boolean useStrictComparation) {
434         return (useStrictComparation ? compareStrictly(a, b) : compareLoosely(a, b));
435     }
436 
437     /**
438      * Compare phone numbers a and b, return true if they're identical
439      * enough for caller ID purposes.
440      *
441      * - Compares from right to left
442      * - requires MIN_MATCH (7) characters to match
443      * - handles common trunk prefixes and international prefixes
444      *   (basically, everything except the Russian trunk prefix)
445      *
446      * Note that this method does not return false even when the two phone numbers
447      * are not exactly same; rather; we can call this method "similar()", not "equals()".
448      *
449      * @hide
450      */
451     public static boolean
compareLoosely(String a, String b)452     compareLoosely(String a, String b) {
453         int ia, ib;
454         int matched;
455         int numNonDialableCharsInA = 0;
456         int numNonDialableCharsInB = 0;
457 
458         if (a == null || b == null) return a == b;
459 
460         if (a.length() == 0 || b.length() == 0) {
461             return false;
462         }
463 
464         ia = indexOfLastNetworkChar (a);
465         ib = indexOfLastNetworkChar (b);
466         matched = 0;
467 
468         while (ia >= 0 && ib >=0) {
469             char ca, cb;
470             boolean skipCmp = false;
471 
472             ca = a.charAt(ia);
473 
474             if (!isDialable(ca)) {
475                 ia--;
476                 skipCmp = true;
477                 numNonDialableCharsInA++;
478             }
479 
480             cb = b.charAt(ib);
481 
482             if (!isDialable(cb)) {
483                 ib--;
484                 skipCmp = true;
485                 numNonDialableCharsInB++;
486             }
487 
488             if (!skipCmp) {
489                 if (cb != ca && ca != WILD && cb != WILD) {
490                     break;
491                 }
492                 ia--; ib--; matched++;
493             }
494         }
495 
496         if (matched < MIN_MATCH) {
497             int effectiveALen = a.length() - numNonDialableCharsInA;
498             int effectiveBLen = b.length() - numNonDialableCharsInB;
499 
500 
501             // if the number of dialable chars in a and b match, but the matched chars < MIN_MATCH,
502             // treat them as equal (i.e. 404-04 and 40404)
503             if (effectiveALen == effectiveBLen && effectiveALen == matched) {
504                 return true;
505             }
506 
507             return false;
508         }
509 
510         // At least one string has matched completely;
511         if (matched >= MIN_MATCH && (ia < 0 || ib < 0)) {
512             return true;
513         }
514 
515         /*
516          * Now, what remains must be one of the following for a
517          * match:
518          *
519          *  - a '+' on one and a '00' or a '011' on the other
520          *  - a '0' on one and a (+,00)<country code> on the other
521          *     (for this, a '0' and a '00' prefix would have succeeded above)
522          */
523 
524         if (matchIntlPrefix(a, ia + 1)
525             && matchIntlPrefix (b, ib +1)
526         ) {
527             return true;
528         }
529 
530         if (matchTrunkPrefix(a, ia + 1)
531             && matchIntlPrefixAndCC(b, ib +1)
532         ) {
533             return true;
534         }
535 
536         if (matchTrunkPrefix(b, ib + 1)
537             && matchIntlPrefixAndCC(a, ia +1)
538         ) {
539             return true;
540         }
541 
542         return false;
543     }
544 
545     /**
546      * @hide
547      */
548     public static boolean
compareStrictly(String a, String b)549     compareStrictly(String a, String b) {
550         return compareStrictly(a, b, true);
551     }
552 
553     /**
554      * @hide
555      */
556     public static boolean
compareStrictly(String a, String b, boolean acceptInvalidCCCPrefix)557     compareStrictly(String a, String b, boolean acceptInvalidCCCPrefix) {
558         if (a == null || b == null) {
559             return a == b;
560         } else if (a.length() == 0 && b.length() == 0) {
561             return false;
562         }
563 
564         int forwardIndexA = 0;
565         int forwardIndexB = 0;
566 
567         CountryCallingCodeAndNewIndex cccA =
568             tryGetCountryCallingCodeAndNewIndex(a, acceptInvalidCCCPrefix);
569         CountryCallingCodeAndNewIndex cccB =
570             tryGetCountryCallingCodeAndNewIndex(b, acceptInvalidCCCPrefix);
571         boolean bothHasCountryCallingCode = false;
572         boolean okToIgnorePrefix = true;
573         boolean trunkPrefixIsOmittedA = false;
574         boolean trunkPrefixIsOmittedB = false;
575         if (cccA != null && cccB != null) {
576             if (cccA.countryCallingCode != cccB.countryCallingCode) {
577                 // Different Country Calling Code. Must be different phone number.
578                 return false;
579             }
580             // When both have ccc, do not ignore trunk prefix. Without this,
581             // "+81123123" becomes same as "+810123123" (+81 == Japan)
582             okToIgnorePrefix = false;
583             bothHasCountryCallingCode = true;
584             forwardIndexA = cccA.newIndex;
585             forwardIndexB = cccB.newIndex;
586         } else if (cccA == null && cccB == null) {
587             // When both do not have ccc, do not ignore trunk prefix. Without this,
588             // "123123" becomes same as "0123123"
589             okToIgnorePrefix = false;
590         } else {
591             if (cccA != null) {
592                 forwardIndexA = cccA.newIndex;
593             } else {
594                 int tmp = tryGetTrunkPrefixOmittedIndex(b, 0);
595                 if (tmp >= 0) {
596                     forwardIndexA = tmp;
597                     trunkPrefixIsOmittedA = true;
598                 }
599             }
600             if (cccB != null) {
601                 forwardIndexB = cccB.newIndex;
602             } else {
603                 int tmp = tryGetTrunkPrefixOmittedIndex(b, 0);
604                 if (tmp >= 0) {
605                     forwardIndexB = tmp;
606                     trunkPrefixIsOmittedB = true;
607                 }
608             }
609         }
610 
611         int backwardIndexA = a.length() - 1;
612         int backwardIndexB = b.length() - 1;
613         while (backwardIndexA >= forwardIndexA && backwardIndexB >= forwardIndexB) {
614             boolean skip_compare = false;
615             final char chA = a.charAt(backwardIndexA);
616             final char chB = b.charAt(backwardIndexB);
617             if (isSeparator(chA)) {
618                 backwardIndexA--;
619                 skip_compare = true;
620             }
621             if (isSeparator(chB)) {
622                 backwardIndexB--;
623                 skip_compare = true;
624             }
625 
626             if (!skip_compare) {
627                 if (chA != chB) {
628                     return false;
629                 }
630                 backwardIndexA--;
631                 backwardIndexB--;
632             }
633         }
634 
635         if (okToIgnorePrefix) {
636             if ((trunkPrefixIsOmittedA && forwardIndexA <= backwardIndexA) ||
637                 !checkPrefixIsIgnorable(a, forwardIndexA, backwardIndexA)) {
638                 if (acceptInvalidCCCPrefix) {
639                     // Maybe the code handling the special case for Thailand makes the
640                     // result garbled, so disable the code and try again.
641                     // e.g. "16610001234" must equal to "6610001234", but with
642                     //      Thailand-case handling code, they become equal to each other.
643                     //
644                     // Note: we select simplicity rather than adding some complicated
645                     //       logic here for performance(like "checking whether remaining
646                     //       numbers are just 66 or not"), assuming inputs are small
647                     //       enough.
648                     return compare(a, b, false);
649                 } else {
650                     return false;
651                 }
652             }
653             if ((trunkPrefixIsOmittedB && forwardIndexB <= backwardIndexB) ||
654                 !checkPrefixIsIgnorable(b, forwardIndexA, backwardIndexB)) {
655                 if (acceptInvalidCCCPrefix) {
656                     return compare(a, b, false);
657                 } else {
658                     return false;
659                 }
660             }
661         } else {
662             // In the US, 1-650-555-1234 must be equal to 650-555-1234,
663             // while 090-1234-1234 must not be equal to 90-1234-1234 in Japan.
664             // This request exists just in US (with 1 trunk (NDD) prefix).
665             // In addition, "011 11 7005554141" must not equal to "+17005554141",
666             // while "011 1 7005554141" must equal to "+17005554141"
667             //
668             // In this comparison, we ignore the prefix '1' just once, when
669             // - at least either does not have CCC, or
670             // - the remaining non-separator number is 1
671             boolean maybeNamp = !bothHasCountryCallingCode;
672             while (backwardIndexA >= forwardIndexA) {
673                 final char chA = a.charAt(backwardIndexA);
674                 if (isDialable(chA)) {
675                     if (maybeNamp && tryGetISODigit(chA) == 1) {
676                         maybeNamp = false;
677                     } else {
678                         return false;
679                     }
680                 }
681                 backwardIndexA--;
682             }
683             while (backwardIndexB >= forwardIndexB) {
684                 final char chB = b.charAt(backwardIndexB);
685                 if (isDialable(chB)) {
686                     if (maybeNamp && tryGetISODigit(chB) == 1) {
687                         maybeNamp = false;
688                     } else {
689                         return false;
690                     }
691                 }
692                 backwardIndexB--;
693             }
694         }
695 
696         return true;
697     }
698 
699     /**
700      * Returns the rightmost MIN_MATCH (5) characters in the network portion
701      * in *reversed* order
702      *
703      * This can be used to do a database lookup against the column
704      * that stores getStrippedReversed()
705      *
706      * Returns null if phoneNumber == null
707      */
708     public static String
toCallerIDMinMatch(String phoneNumber)709     toCallerIDMinMatch(String phoneNumber) {
710         String np = extractNetworkPortionAlt(phoneNumber);
711         return internalGetStrippedReversed(np, MIN_MATCH);
712     }
713 
714     /**
715      * Returns the network portion reversed.
716      * This string is intended to go into an index column for a
717      * database lookup.
718      *
719      * Returns null if phoneNumber == null
720      */
721     public static String
getStrippedReversed(String phoneNumber)722     getStrippedReversed(String phoneNumber) {
723         String np = extractNetworkPortionAlt(phoneNumber);
724 
725         if (np == null) return null;
726 
727         return internalGetStrippedReversed(np, np.length());
728     }
729 
730     /**
731      * Returns the last numDigits of the reversed phone number
732      * Returns null if np == null
733      */
734     private static String
internalGetStrippedReversed(String np, int numDigits)735     internalGetStrippedReversed(String np, int numDigits) {
736         if (np == null) return null;
737 
738         StringBuilder ret = new StringBuilder(numDigits);
739         int length = np.length();
740 
741         for (int i = length - 1, s = length
742             ; i >= 0 && (s - i) <= numDigits ; i--
743         ) {
744             char c = np.charAt(i);
745 
746             ret.append(c);
747         }
748 
749         return ret.toString();
750     }
751 
752     /**
753      * Basically: makes sure there's a + in front of a
754      * TOA_International number
755      *
756      * Returns null if s == null
757      */
758     public static String
stringFromStringAndTOA(String s, int TOA)759     stringFromStringAndTOA(String s, int TOA) {
760         if (s == null) return null;
761 
762         if (TOA == TOA_International && s.length() > 0 && s.charAt(0) != '+') {
763             return "+" + s;
764         }
765 
766         return s;
767     }
768 
769     /**
770      * Returns the TOA for the given dial string
771      * Basically, returns TOA_International if there's a + prefix
772      */
773 
774     public static int
toaFromString(String s)775     toaFromString(String s) {
776         if (s != null && s.length() > 0 && s.charAt(0) == '+') {
777             return TOA_International;
778         }
779 
780         return TOA_Unknown;
781     }
782 
783     /**
784      *  3GPP TS 24.008 10.5.4.7
785      *  Called Party BCD Number
786      *
787      *  See Also TS 51.011 10.5.1 "dialing number/ssc string"
788      *  and TS 11.11 "10.3.1 EF adn (Abbreviated dialing numbers)"
789      *
790      * @param bytes the data buffer
791      * @param offset should point to the TOA (aka. TON/NPI) octet after the length byte
792      * @param length is the number of bytes including TOA byte
793      *                and must be at least 2
794      *
795      * @return partial string on invalid decode
796      *
797      * FIXME(mkf) support alphanumeric address type
798      *  currently implemented in SMSMessage.getAddress()
799      */
800     public static String
calledPartyBCDToString(byte[] bytes, int offset, int length)801     calledPartyBCDToString (byte[] bytes, int offset, int length) {
802         boolean prependPlus = false;
803         StringBuilder ret = new StringBuilder(1 + length * 2);
804 
805         if (length < 2) {
806             return "";
807         }
808 
809         //Only TON field should be taken in consideration
810         if ((bytes[offset] & 0xf0) == (TOA_International & 0xf0)) {
811             prependPlus = true;
812         }
813 
814         internalCalledPartyBCDFragmentToString(
815                 ret, bytes, offset + 1, length - 1);
816 
817         if (prependPlus && ret.length() == 0) {
818             // If the only thing there is a prepended plus, return ""
819             return "";
820         }
821 
822         if (prependPlus) {
823             // This is an "international number" and should have
824             // a plus prepended to the dialing number. But there
825             // can also be GSM MMI codes as defined in TS 22.030 6.5.2
826             // so we need to handle those also.
827             //
828             // http://web.telia.com/~u47904776/gsmkode.htm
829             // has a nice list of some of these GSM codes.
830             //
831             // Examples are:
832             //   **21*+886988171479#
833             //   **21*8311234567#
834             //   *21#
835             //   #21#
836             //   *#21#
837             //   *31#+11234567890
838             //   #31#+18311234567
839             //   #31#8311234567
840             //   18311234567
841             //   +18311234567#
842             //   +18311234567
843             // Odd ball cases that some phones handled
844             // where there is no dialing number so they
845             // append the "+"
846             //   *21#+
847             //   **21#+
848             String retString = ret.toString();
849             Pattern p = Pattern.compile("(^[#*])(.*)([#*])(.*)(#)$");
850             Matcher m = p.matcher(retString);
851             if (m.matches()) {
852                 if ("".equals(m.group(2))) {
853                     // Started with two [#*] ends with #
854                     // So no dialing number and we'll just
855                     // append a +, this handles **21#+
856                     ret = new StringBuilder();
857                     ret.append(m.group(1));
858                     ret.append(m.group(3));
859                     ret.append(m.group(4));
860                     ret.append(m.group(5));
861                     ret.append("+");
862                 } else {
863                     // Starts with [#*] and ends with #
864                     // Assume group 4 is a dialing number
865                     // such as *21*+1234554#
866                     ret = new StringBuilder();
867                     ret.append(m.group(1));
868                     ret.append(m.group(2));
869                     ret.append(m.group(3));
870                     ret.append("+");
871                     ret.append(m.group(4));
872                     ret.append(m.group(5));
873                 }
874             } else {
875                 p = Pattern.compile("(^[#*])(.*)([#*])(.*)");
876                 m = p.matcher(retString);
877                 if (m.matches()) {
878                     // Starts with [#*] and only one other [#*]
879                     // Assume the data after last [#*] is dialing
880                     // number (i.e. group 4) such as *31#+11234567890.
881                     // This also includes the odd ball *21#+
882                     ret = new StringBuilder();
883                     ret.append(m.group(1));
884                     ret.append(m.group(2));
885                     ret.append(m.group(3));
886                     ret.append("+");
887                     ret.append(m.group(4));
888                 } else {
889                     // Does NOT start with [#*] just prepend '+'
890                     ret = new StringBuilder();
891                     ret.append('+');
892                     ret.append(retString);
893                 }
894             }
895         }
896 
897         return ret.toString();
898     }
899 
900     private static void
internalCalledPartyBCDFragmentToString( StringBuilder sb, byte [] bytes, int offset, int length)901     internalCalledPartyBCDFragmentToString(
902         StringBuilder sb, byte [] bytes, int offset, int length) {
903         for (int i = offset ; i < length + offset ; i++) {
904             byte b;
905             char c;
906 
907             c = bcdToChar((byte)(bytes[i] & 0xf));
908 
909             if (c == 0) {
910                 return;
911             }
912             sb.append(c);
913 
914             // FIXME(mkf) TS 23.040 9.1.2.3 says
915             // "if a mobile receives 1111 in a position prior to
916             // the last semi-octet then processing shall commence with
917             // the next semi-octet and the intervening
918             // semi-octet shall be ignored"
919             // How does this jive with 24.008 10.5.4.7
920 
921             b = (byte)((bytes[i] >> 4) & 0xf);
922 
923             if (b == 0xf && i + 1 == length + offset) {
924                 //ignore final 0xf
925                 break;
926             }
927 
928             c = bcdToChar(b);
929             if (c == 0) {
930                 return;
931             }
932 
933             sb.append(c);
934         }
935 
936     }
937 
938     /**
939      * Like calledPartyBCDToString, but field does not start with a
940      * TOA byte. For example: SIM ADN extension fields
941      */
942 
943     public static String
calledPartyBCDFragmentToString(byte [] bytes, int offset, int length)944     calledPartyBCDFragmentToString(byte [] bytes, int offset, int length) {
945         StringBuilder ret = new StringBuilder(length * 2);
946 
947         internalCalledPartyBCDFragmentToString(ret, bytes, offset, length);
948 
949         return ret.toString();
950     }
951 
952     /** returns 0 on invalid value */
953     private static char
bcdToChar(byte b)954     bcdToChar(byte b) {
955         if (b < 0xa) {
956             return (char)('0' + b);
957         } else switch (b) {
958             case 0xa: return '*';
959             case 0xb: return '#';
960             case 0xc: return PAUSE;
961             case 0xd: return WILD;
962 
963             default: return 0;
964         }
965     }
966 
967     private static int
charToBCD(char c)968     charToBCD(char c) {
969         if (c >= '0' && c <= '9') {
970             return c - '0';
971         } else if (c == '*') {
972             return 0xa;
973         } else if (c == '#') {
974             return 0xb;
975         } else if (c == PAUSE) {
976             return 0xc;
977         } else if (c == WILD) {
978             return 0xd;
979         } else {
980             throw new RuntimeException ("invalid char for BCD " + c);
981         }
982     }
983 
984     /**
985      * Return true iff the network portion of <code>address</code> is,
986      * as far as we can tell on the device, suitable for use as an SMS
987      * destination address.
988      */
isWellFormedSmsAddress(String address)989     public static boolean isWellFormedSmsAddress(String address) {
990         String networkPortion =
991                 PhoneNumberUtils.extractNetworkPortion(address);
992 
993         return (!(networkPortion.equals("+")
994                   || TextUtils.isEmpty(networkPortion)))
995                && isDialable(networkPortion);
996     }
997 
isGlobalPhoneNumber(String phoneNumber)998     public static boolean isGlobalPhoneNumber(String phoneNumber) {
999         if (TextUtils.isEmpty(phoneNumber)) {
1000             return false;
1001         }
1002 
1003         Matcher match = GLOBAL_PHONE_NUMBER_PATTERN.matcher(phoneNumber);
1004         return match.matches();
1005     }
1006 
isDialable(String address)1007     private static boolean isDialable(String address) {
1008         for (int i = 0, count = address.length(); i < count; i++) {
1009             if (!isDialable(address.charAt(i))) {
1010                 return false;
1011             }
1012         }
1013         return true;
1014     }
1015 
isNonSeparator(String address)1016     private static boolean isNonSeparator(String address) {
1017         for (int i = 0, count = address.length(); i < count; i++) {
1018             if (!isNonSeparator(address.charAt(i))) {
1019                 return false;
1020             }
1021         }
1022         return true;
1023     }
1024     /**
1025      * Note: calls extractNetworkPortion(), so do not use for
1026      * SIM EF[ADN] style records
1027      *
1028      * Returns null if network portion is empty.
1029      */
1030     public static byte[]
networkPortionToCalledPartyBCD(String s)1031     networkPortionToCalledPartyBCD(String s) {
1032         String networkPortion = extractNetworkPortion(s);
1033         return numberToCalledPartyBCDHelper(networkPortion, false);
1034     }
1035 
1036     /**
1037      * Same as {@link #networkPortionToCalledPartyBCD}, but includes a
1038      * one-byte length prefix.
1039      */
1040     public static byte[]
networkPortionToCalledPartyBCDWithLength(String s)1041     networkPortionToCalledPartyBCDWithLength(String s) {
1042         String networkPortion = extractNetworkPortion(s);
1043         return numberToCalledPartyBCDHelper(networkPortion, true);
1044     }
1045 
1046     /**
1047      * Convert a dialing number to BCD byte array
1048      *
1049      * @param number dialing number string
1050      *        if the dialing number starts with '+', set to international TOA
1051      * @return BCD byte array
1052      */
1053     public static byte[]
numberToCalledPartyBCD(String number)1054     numberToCalledPartyBCD(String number) {
1055         return numberToCalledPartyBCDHelper(number, false);
1056     }
1057 
1058     /**
1059      * If includeLength is true, prepend a one-byte length value to
1060      * the return array.
1061      */
1062     private static byte[]
numberToCalledPartyBCDHelper(String number, boolean includeLength)1063     numberToCalledPartyBCDHelper(String number, boolean includeLength) {
1064         int numberLenReal = number.length();
1065         int numberLenEffective = numberLenReal;
1066         boolean hasPlus = number.indexOf('+') != -1;
1067         if (hasPlus) numberLenEffective--;
1068 
1069         if (numberLenEffective == 0) return null;
1070 
1071         int resultLen = (numberLenEffective + 1) / 2;  // Encoded numbers require only 4 bits each.
1072         int extraBytes = 1;                            // Prepended TOA byte.
1073         if (includeLength) extraBytes++;               // Optional prepended length byte.
1074         resultLen += extraBytes;
1075 
1076         byte[] result = new byte[resultLen];
1077 
1078         int digitCount = 0;
1079         for (int i = 0; i < numberLenReal; i++) {
1080             char c = number.charAt(i);
1081             if (c == '+') continue;
1082             int shift = ((digitCount & 0x01) == 1) ? 4 : 0;
1083             result[extraBytes + (digitCount >> 1)] |= (byte)((charToBCD(c) & 0x0F) << shift);
1084             digitCount++;
1085         }
1086 
1087         // 1-fill any trailing odd nibble/quartet.
1088         if ((digitCount & 0x01) == 1) result[extraBytes + (digitCount >> 1)] |= 0xF0;
1089 
1090         int offset = 0;
1091         if (includeLength) result[offset++] = (byte)(resultLen - 1);
1092         result[offset] = (byte)(hasPlus ? TOA_International : TOA_Unknown);
1093 
1094         return result;
1095     }
1096 
1097     //================ Number formatting =========================
1098 
1099     /** The current locale is unknown, look for a country code or don't format */
1100     public static final int FORMAT_UNKNOWN = 0;
1101     /** NANP formatting */
1102     public static final int FORMAT_NANP = 1;
1103     /** Japanese formatting */
1104     public static final int FORMAT_JAPAN = 2;
1105 
1106     /** List of country codes for countries that use the NANP */
1107     private static final String[] NANP_COUNTRIES = new String[] {
1108         "US", // United States
1109         "CA", // Canada
1110         "AS", // American Samoa
1111         "AI", // Anguilla
1112         "AG", // Antigua and Barbuda
1113         "BS", // Bahamas
1114         "BB", // Barbados
1115         "BM", // Bermuda
1116         "VG", // British Virgin Islands
1117         "KY", // Cayman Islands
1118         "DM", // Dominica
1119         "DO", // Dominican Republic
1120         "GD", // Grenada
1121         "GU", // Guam
1122         "JM", // Jamaica
1123         "PR", // Puerto Rico
1124         "MS", // Montserrat
1125         "MP", // Northern Mariana Islands
1126         "KN", // Saint Kitts and Nevis
1127         "LC", // Saint Lucia
1128         "VC", // Saint Vincent and the Grenadines
1129         "TT", // Trinidad and Tobago
1130         "TC", // Turks and Caicos Islands
1131         "VI", // U.S. Virgin Islands
1132     };
1133 
1134     /**
1135      * Breaks the given number down and formats it according to the rules
1136      * for the country the number is from.
1137      *
1138      * @param source The phone number to format
1139      * @return A locally acceptable formatting of the input, or the raw input if
1140      *  formatting rules aren't known for the number
1141      */
formatNumber(String source)1142     public static String formatNumber(String source) {
1143         SpannableStringBuilder text = new SpannableStringBuilder(source);
1144         formatNumber(text, getFormatTypeForLocale(Locale.getDefault()));
1145         return text.toString();
1146     }
1147 
1148     /**
1149      * Formats the given number with the given formatting type. Currently
1150      * {@link #FORMAT_NANP} and {@link #FORMAT_JAPAN} are supported as a formating type.
1151      *
1152      * @param source the phone number to format
1153      * @param defaultFormattingType The default formatting rules to apply if the number does
1154      * not begin with +[country_code]
1155      * @return The phone number formatted with the given formatting type.
1156      *
1157      * @hide TODO: Should be unhidden.
1158      */
formatNumber(String source, int defaultFormattingType)1159     public static String formatNumber(String source, int defaultFormattingType) {
1160         SpannableStringBuilder text = new SpannableStringBuilder(source);
1161         formatNumber(text, defaultFormattingType);
1162         return text.toString();
1163     }
1164 
1165     /**
1166      * Returns the phone number formatting type for the given locale.
1167      *
1168      * @param locale The locale of interest, usually {@link Locale#getDefault()}
1169      * @return The formatting type for the given locale, or FORMAT_UNKNOWN if the formatting
1170      * rules are not known for the given locale
1171      */
getFormatTypeForLocale(Locale locale)1172     public static int getFormatTypeForLocale(Locale locale) {
1173         String country = locale.getCountry();
1174 
1175         return getFormatTypeFromCountryCode(country);
1176     }
1177 
1178     /**
1179      * Formats a phone number in-place. Currently {@link #FORMAT_JAPAN} and {@link #FORMAT_NANP}
1180      * is supported as a second argument.
1181      *
1182      * @param text The number to be formatted, will be modified with the formatting
1183      * @param defaultFormattingType The default formatting rules to apply if the number does
1184      * not begin with +[country_code]
1185      */
formatNumber(Editable text, int defaultFormattingType)1186     public static void formatNumber(Editable text, int defaultFormattingType) {
1187         int formatType = defaultFormattingType;
1188 
1189         if (text.length() > 2 && text.charAt(0) == '+') {
1190             if (text.charAt(1) == '1') {
1191                 formatType = FORMAT_NANP;
1192             } else if (text.length() >= 3 && text.charAt(1) == '8'
1193                 && text.charAt(2) == '1') {
1194                 formatType = FORMAT_JAPAN;
1195             } else {
1196                 formatType = FORMAT_UNKNOWN;
1197             }
1198         }
1199 
1200         switch (formatType) {
1201             case FORMAT_NANP:
1202                 formatNanpNumber(text);
1203                 return;
1204             case FORMAT_JAPAN:
1205                 formatJapaneseNumber(text);
1206                 return;
1207             case FORMAT_UNKNOWN:
1208                 removeDashes(text);
1209                 return;
1210         }
1211     }
1212 
1213     private static final int NANP_STATE_DIGIT = 1;
1214     private static final int NANP_STATE_PLUS = 2;
1215     private static final int NANP_STATE_ONE = 3;
1216     private static final int NANP_STATE_DASH = 4;
1217 
1218     /**
1219      * Formats a phone number in-place using the NANP formatting rules. Numbers will be formatted
1220      * as:
1221      *
1222      * <p><code>
1223      * xxxxx
1224      * xxx-xxxx
1225      * xxx-xxx-xxxx
1226      * 1-xxx-xxx-xxxx
1227      * +1-xxx-xxx-xxxx
1228      * </code></p>
1229      *
1230      * @param text the number to be formatted, will be modified with the formatting
1231      */
formatNanpNumber(Editable text)1232     public static void formatNanpNumber(Editable text) {
1233         int length = text.length();
1234         if (length > "+1-nnn-nnn-nnnn".length()) {
1235             // The string is too long to be formatted
1236             return;
1237         } else if (length <= 5) {
1238             // The string is either a shortcode or too short to be formatted
1239             return;
1240         }
1241 
1242         CharSequence saved = text.subSequence(0, length);
1243 
1244         // Strip the dashes first, as we're going to add them back
1245         removeDashes(text);
1246         length = text.length();
1247 
1248         // When scanning the number we record where dashes need to be added,
1249         // if they're non-0 at the end of the scan the dashes will be added in
1250         // the proper places.
1251         int dashPositions[] = new int[3];
1252         int numDashes = 0;
1253 
1254         int state = NANP_STATE_DIGIT;
1255         int numDigits = 0;
1256         for (int i = 0; i < length; i++) {
1257             char c = text.charAt(i);
1258             switch (c) {
1259                 case '1':
1260                     if (numDigits == 0 || state == NANP_STATE_PLUS) {
1261                         state = NANP_STATE_ONE;
1262                         break;
1263                     }
1264                     // fall through
1265                 case '2':
1266                 case '3':
1267                 case '4':
1268                 case '5':
1269                 case '6':
1270                 case '7':
1271                 case '8':
1272                 case '9':
1273                 case '0':
1274                     if (state == NANP_STATE_PLUS) {
1275                         // Only NANP number supported for now
1276                         text.replace(0, length, saved);
1277                         return;
1278                     } else if (state == NANP_STATE_ONE) {
1279                         // Found either +1 or 1, follow it up with a dash
1280                         dashPositions[numDashes++] = i;
1281                     } else if (state != NANP_STATE_DASH && (numDigits == 3 || numDigits == 6)) {
1282                         // Found a digit that should be after a dash that isn't
1283                         dashPositions[numDashes++] = i;
1284                     }
1285                     state = NANP_STATE_DIGIT;
1286                     numDigits++;
1287                     break;
1288 
1289                 case '-':
1290                     state = NANP_STATE_DASH;
1291                     break;
1292 
1293                 case '+':
1294                     if (i == 0) {
1295                         // Plus is only allowed as the first character
1296                         state = NANP_STATE_PLUS;
1297                         break;
1298                     }
1299                     // Fall through
1300                 default:
1301                     // Unknown character, bail on formatting
1302                     text.replace(0, length, saved);
1303                     return;
1304             }
1305         }
1306 
1307         if (numDigits == 7) {
1308             // With 7 digits we want xxx-xxxx, not xxx-xxx-x
1309             numDashes--;
1310         }
1311 
1312         // Actually put the dashes in place
1313         for (int i = 0; i < numDashes; i++) {
1314             int pos = dashPositions[i];
1315             text.replace(pos + i, pos + i, "-");
1316         }
1317 
1318         // Remove trailing dashes
1319         int len = text.length();
1320         while (len > 0) {
1321             if (text.charAt(len - 1) == '-') {
1322                 text.delete(len - 1, len);
1323                 len--;
1324             } else {
1325                 break;
1326             }
1327         }
1328     }
1329 
1330     /**
1331      * Formats a phone number in-place using the Japanese formatting rules.
1332      * Numbers will be formatted as:
1333      *
1334      * <p><code>
1335      * 03-xxxx-xxxx
1336      * 090-xxxx-xxxx
1337      * 0120-xxx-xxx
1338      * +81-3-xxxx-xxxx
1339      * +81-90-xxxx-xxxx
1340      * </code></p>
1341      *
1342      * @param text the number to be formatted, will be modified with
1343      * the formatting
1344      */
formatJapaneseNumber(Editable text)1345     public static void formatJapaneseNumber(Editable text) {
1346         JapanesePhoneNumberFormatter.format(text);
1347     }
1348 
1349     /**
1350      * Removes all dashes from the number.
1351      *
1352      * @param text the number to clear from dashes
1353      */
removeDashes(Editable text)1354     private static void removeDashes(Editable text) {
1355         int p = 0;
1356         while (p < text.length()) {
1357             if (text.charAt(p) == '-') {
1358                 text.delete(p, p + 1);
1359            } else {
1360                 p++;
1361            }
1362         }
1363     }
1364 
1365     /**
1366      * Format the given phoneNumber to the E.164 representation.
1367      * <p>
1368      * The given phone number must have an area code and could have a country
1369      * code.
1370      * <p>
1371      * The defaultCountryIso is used to validate the given number and generate
1372      * the E.164 phone number if the given number doesn't have a country code.
1373      *
1374      * @param phoneNumber
1375      *            the phone number to format
1376      * @param defaultCountryIso
1377      *            the ISO 3166-1 two letters country code
1378      * @return the E.164 representation, or null if the given phone number is
1379      *         not valid.
1380      *
1381      * @hide
1382      */
formatNumberToE164(String phoneNumber, String defaultCountryIso)1383     public static String formatNumberToE164(String phoneNumber, String defaultCountryIso) {
1384         PhoneNumberUtil util = PhoneNumberUtil.getInstance();
1385         String result = null;
1386         try {
1387             PhoneNumber pn = util.parse(phoneNumber, defaultCountryIso);
1388             if (util.isValidNumber(pn)) {
1389                 result = util.format(pn, PhoneNumberFormat.E164);
1390             }
1391         } catch (NumberParseException e) {
1392         }
1393         return result;
1394     }
1395 
1396     /**
1397      * Format a phone number.
1398      * <p>
1399      * If the given number doesn't have the country code, the phone will be
1400      * formatted to the default country's convention.
1401      *
1402      * @param phoneNumber
1403      *            the number to be formatted.
1404      * @param defaultCountryIso
1405      *            the ISO 3166-1 two letters country code whose convention will
1406      *            be used if the given number doesn't have the country code.
1407      * @return the formatted number, or null if the given number is not valid.
1408      *
1409      * @hide
1410      */
formatNumber(String phoneNumber, String defaultCountryIso)1411     public static String formatNumber(String phoneNumber, String defaultCountryIso) {
1412         // Do not attempt to format numbers that start with a hash or star symbol.
1413         if (phoneNumber.startsWith("#") || phoneNumber.startsWith("*")) {
1414             return phoneNumber;
1415         }
1416 
1417         PhoneNumberUtil util = PhoneNumberUtil.getInstance();
1418         String result = null;
1419         try {
1420             PhoneNumber pn = util.parseAndKeepRawInput(phoneNumber, defaultCountryIso);
1421             result = util.formatInOriginalFormat(pn, defaultCountryIso);
1422         } catch (NumberParseException e) {
1423         }
1424         return result;
1425     }
1426 
1427     /**
1428      * Format the phone number only if the given number hasn't been formatted.
1429      * <p>
1430      * The number which has only dailable character is treated as not being
1431      * formatted.
1432      *
1433      * @param phoneNumber
1434      *            the number to be formatted.
1435      * @param phoneNumberE164
1436      *            the E164 format number whose country code is used if the given
1437      *            phoneNumber doesn't have the country code.
1438      * @param defaultCountryIso
1439      *            the ISO 3166-1 two letters country code whose convention will
1440      *            be used if the phoneNumberE164 is null or invalid.
1441      * @return the formatted number if the given number has been formatted,
1442      *            otherwise, return the given number.
1443      *
1444      * @hide
1445      */
formatNumber( String phoneNumber, String phoneNumberE164, String defaultCountryIso)1446     public static String formatNumber(
1447             String phoneNumber, String phoneNumberE164, String defaultCountryIso) {
1448         int len = phoneNumber.length();
1449         for (int i = 0; i < len; i++) {
1450             if (!isDialable(phoneNumber.charAt(i))) {
1451                 return phoneNumber;
1452             }
1453         }
1454         PhoneNumberUtil util = PhoneNumberUtil.getInstance();
1455         // Get the country code from phoneNumberE164
1456         if (phoneNumberE164 != null && phoneNumberE164.length() >= 2
1457                 && phoneNumberE164.charAt(0) == '+') {
1458             try {
1459                 PhoneNumber pn = util.parse(phoneNumberE164, defaultCountryIso);
1460                 String regionCode = util.getRegionCodeForNumber(pn);
1461                 if (!TextUtils.isEmpty(regionCode)) {
1462                     defaultCountryIso = regionCode;
1463                 }
1464             } catch (NumberParseException e) {
1465             }
1466         }
1467         String result = formatNumber(phoneNumber, defaultCountryIso);
1468         return result != null ? result : phoneNumber;
1469     }
1470 
1471     /**
1472      * Normalize a phone number by removing the characters other than digits. If
1473      * the given number has keypad letters, the letters will be converted to
1474      * digits first.
1475      *
1476      * @param phoneNumber
1477      *            the number to be normalized.
1478      * @return the normalized number.
1479      *
1480      * @hide
1481      */
normalizeNumber(String phoneNumber)1482     public static String normalizeNumber(String phoneNumber) {
1483         StringBuilder sb = new StringBuilder();
1484         int len = phoneNumber.length();
1485         for (int i = 0; i < len; i++) {
1486             char c = phoneNumber.charAt(i);
1487             if ((i == 0 && c == '+') || PhoneNumberUtils.isISODigit(c)) {
1488                 sb.append(c);
1489             } else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
1490                 return normalizeNumber(PhoneNumberUtils.convertKeypadLettersToDigits(phoneNumber));
1491             }
1492         }
1493         return sb.toString();
1494     }
1495 
1496     // Three and four digit phone numbers for either special services,
1497     // or 3-6 digit addresses from the network (eg carrier-originated SMS messages) should
1498     // not match.
1499     //
1500     // This constant used to be 5, but SMS short codes has increased in length and
1501     // can be easily 6 digits now days. Most countries have SMS short code length between
1502     // 3 to 6 digits. The exceptions are
1503     //
1504     // Australia: Short codes are six or eight digits in length, starting with the prefix "19"
1505     //            followed by an additional four or six digits and two.
1506     // Czech Republic: Codes are seven digits in length for MO and five (not billed) or
1507     //            eight (billed) for MT direction
1508     //
1509     // see http://en.wikipedia.org/wiki/Short_code#Regional_differences for reference
1510     //
1511     // However, in order to loose match 650-555-1212 and 555-1212, we need to set the min match
1512     // to 7.
1513     static final int MIN_MATCH = 7;
1514 
1515     /**
1516      * isEmergencyNumber: checks a given number against the list of
1517      *   emergency numbers provided by the RIL and SIM card.
1518      *
1519      * @param number the number to look up.
1520      * @return if the number is in the list of emergency numbers
1521      * listed in the ril / sim, then return true, otherwise false.
1522      */
isEmergencyNumber(String number)1523     public static boolean isEmergencyNumber(String number) {
1524         // If the number passed in is null, just return false:
1525         if (number == null) return false;
1526 
1527         // Strip the separators from the number before comparing it
1528         // to the list.
1529         number = extractNetworkPortionAlt(number);
1530 
1531         // retrieve the list of emergency numbers
1532         // check read-write ecclist property first
1533         String numbers = SystemProperties.get("ril.ecclist");
1534         if (TextUtils.isEmpty(numbers)) {
1535             // then read-only ecclist property since old RIL only uses this
1536             numbers = SystemProperties.get("ro.ril.ecclist");
1537         }
1538 
1539         if (!TextUtils.isEmpty(numbers)) {
1540             // searches through the comma-separated list for a match,
1541             // return true if one is found.
1542             for (String emergencyNum : numbers.split(",")) {
1543                 if (number.startsWith(emergencyNum)) {
1544                     return true;
1545                 }
1546             }
1547             // no matches found against the list!
1548             return false;
1549         }
1550 
1551         //no ecclist system property, so use our own list.
1552         return (number.startsWith("112") || number.startsWith("911"));
1553     }
1554 
1555     /**
1556      * Checks if a given number is an emergency number for a specific country.
1557      *
1558      * @param number the number to look up.
1559      * @param defaultCountryIso the specific country which the number should be checked against
1560      * @return if the number is an emergency number for the specific country, then return true,
1561      * otherwise false
1562      * @hide
1563      */
isEmergencyNumber(String number, String defaultCountryIso)1564     public static boolean isEmergencyNumber(String number, String defaultCountryIso) {
1565       PhoneNumberUtil util = PhoneNumberUtil.getInstance();
1566       try {
1567         PhoneNumber pn = util.parse(number, defaultCountryIso);
1568         // libphonenumber guarantees short numbers such as emergency numbers are classified as
1569         // invalid. Therefore, if the number passes the validation test, we believe it is not an
1570         // emergency number.
1571         // TODO: Compare against a list of country-specific known emergency numbers instead, once
1572         // that has been collected.
1573         if (util.isValidNumber(pn)) {
1574           return false;
1575         } else if ("BR".equalsIgnoreCase(defaultCountryIso) && number.length() >= 8) {
1576           // This is to prevent Brazilian local numbers which start with 911 being incorrectly
1577           // classified as emergency numbers. 911 is not an emergency number in Brazil; it is also
1578           // not possible to append additional digits to an emergency number to dial the number in
1579           // Brazil - it won't connect.
1580           // TODO: Clean this up once a list of country-specific known emergency numbers is
1581           // collected.
1582           return false;
1583         }
1584       } catch (NumberParseException e) {
1585       }
1586       return isEmergencyNumber(number);
1587     }
1588 
1589     /**
1590      * Checks if a given number is an emergency number for the country that the user is in. The
1591      * current country is determined using the CountryDetector.
1592      *
1593      * @param number the number to look up.
1594      * @param context the specific context which the number should be checked against
1595      * @return if a phone number is an emergency number for a local country, based on the
1596      * CountryDetector.
1597      * @see android.location.CountryDetector
1598      * @hide
1599      */
isLocalEmergencyNumber(String number, Context context)1600     public static boolean isLocalEmergencyNumber(String number, Context context) {
1601         String countryIso;
1602         CountryDetector detector = (CountryDetector) context.getSystemService(
1603                 Context.COUNTRY_DETECTOR);
1604         if (detector != null) {
1605             countryIso = detector.detectCountry().getCountryIso();
1606         } else {
1607             Locale locale = context.getResources().getConfiguration().locale;
1608             countryIso = locale.getCountry();
1609             Log.w(LOG_TAG, "No CountryDetector; falling back to countryIso based on locale: "
1610                     + countryIso);
1611         }
1612         return isEmergencyNumber(number, countryIso);
1613     }
1614 
1615     /**
1616      * isVoiceMailNumber: checks a given number against the voicemail
1617      *   number provided by the RIL and SIM card. The caller must have
1618      *   the READ_PHONE_STATE credential.
1619      *
1620      * @param number the number to look up.
1621      * @return true if the number is in the list of voicemail. False
1622      * otherwise, including if the caller does not have the permission
1623      * to read the VM number.
1624      * @hide TODO: pending API Council approval
1625      */
isVoiceMailNumber(String number)1626     public static boolean isVoiceMailNumber(String number) {
1627         String vmNumber;
1628 
1629         try {
1630             vmNumber = TelephonyManager.getDefault().getVoiceMailNumber();
1631         } catch (SecurityException ex) {
1632             return false;
1633         }
1634 
1635         // Strip the separators from the number before comparing it
1636         // to the list.
1637         number = extractNetworkPortionAlt(number);
1638 
1639         // compare tolerates null so we need to make sure that we
1640         // don't return true when both are null.
1641         return !TextUtils.isEmpty(number) && compare(number, vmNumber);
1642     }
1643 
1644     /**
1645      * Translates any alphabetic letters (i.e. [A-Za-z]) in the
1646      * specified phone number into the equivalent numeric digits,
1647      * according to the phone keypad letter mapping described in
1648      * ITU E.161 and ISO/IEC 9995-8.
1649      *
1650      * @return the input string, with alpha letters converted to numeric
1651      *         digits using the phone keypad letter mapping.  For example,
1652      *         an input of "1-800-GOOG-411" will return "1-800-4664-411".
1653      */
convertKeypadLettersToDigits(String input)1654     public static String convertKeypadLettersToDigits(String input) {
1655         if (input == null) {
1656             return input;
1657         }
1658         int len = input.length();
1659         if (len == 0) {
1660             return input;
1661         }
1662 
1663         char[] out = input.toCharArray();
1664 
1665         for (int i = 0; i < len; i++) {
1666             char c = out[i];
1667             // If this char isn't in KEYPAD_MAP at all, just leave it alone.
1668             out[i] = (char) KEYPAD_MAP.get(c, c);
1669         }
1670 
1671         return new String(out);
1672     }
1673 
1674     /**
1675      * The phone keypad letter mapping (see ITU E.161 or ISO/IEC 9995-8.)
1676      * TODO: This should come from a resource.
1677      */
1678     private static final SparseIntArray KEYPAD_MAP = new SparseIntArray();
1679     static {
1680         KEYPAD_MAP.put('a', '2'); KEYPAD_MAP.put('b', '2'); KEYPAD_MAP.put('c', '2');
1681         KEYPAD_MAP.put('A', '2'); KEYPAD_MAP.put('B', '2'); KEYPAD_MAP.put('C', '2');
1682 
1683         KEYPAD_MAP.put('d', '3'); KEYPAD_MAP.put('e', '3'); KEYPAD_MAP.put('f', '3');
1684         KEYPAD_MAP.put('D', '3'); KEYPAD_MAP.put('E', '3'); KEYPAD_MAP.put('F', '3');
1685 
1686         KEYPAD_MAP.put('g', '4'); KEYPAD_MAP.put('h', '4'); KEYPAD_MAP.put('i', '4');
1687         KEYPAD_MAP.put('G', '4'); KEYPAD_MAP.put('H', '4'); KEYPAD_MAP.put('I', '4');
1688 
1689         KEYPAD_MAP.put('j', '5'); KEYPAD_MAP.put('k', '5'); KEYPAD_MAP.put('l', '5');
1690         KEYPAD_MAP.put('J', '5'); KEYPAD_MAP.put('K', '5'); KEYPAD_MAP.put('L', '5');
1691 
1692         KEYPAD_MAP.put('m', '6'); KEYPAD_MAP.put('n', '6'); KEYPAD_MAP.put('o', '6');
1693         KEYPAD_MAP.put('M', '6'); KEYPAD_MAP.put('N', '6'); KEYPAD_MAP.put('O', '6');
1694 
1695         KEYPAD_MAP.put('p', '7'); KEYPAD_MAP.put('q', '7'); KEYPAD_MAP.put('r', '7'); KEYPAD_MAP.put('s', '7');
1696         KEYPAD_MAP.put('P', '7'); KEYPAD_MAP.put('Q', '7'); KEYPAD_MAP.put('R', '7'); KEYPAD_MAP.put('S', '7');
1697 
1698         KEYPAD_MAP.put('t', '8'); KEYPAD_MAP.put('u', '8'); KEYPAD_MAP.put('v', '8');
1699         KEYPAD_MAP.put('T', '8'); KEYPAD_MAP.put('U', '8'); KEYPAD_MAP.put('V', '8');
1700 
1701         KEYPAD_MAP.put('w', '9'); KEYPAD_MAP.put('x', '9'); KEYPAD_MAP.put('y', '9'); KEYPAD_MAP.put('z', '9');
1702         KEYPAD_MAP.put('W', '9'); KEYPAD_MAP.put('X', '9'); KEYPAD_MAP.put('Y', '9'); KEYPAD_MAP.put('Z', '9');
1703     }
1704 
1705     //================ Plus Code formatting =========================
1706     private static final char PLUS_SIGN_CHAR = '+';
1707     private static final String PLUS_SIGN_STRING = "+";
1708     private static final String NANP_IDP_STRING = "011";
1709     private static final int NANP_LENGTH = 10;
1710 
1711     /**
1712      * This function checks if there is a plus sign (+) in the passed-in dialing number.
1713      * If there is, it processes the plus sign based on the default telephone
1714      * numbering plan of the system when the phone is activated and the current
1715      * telephone numbering plan of the system that the phone is camped on.
1716      * Currently, we only support the case that the default and current telephone
1717      * numbering plans are North American Numbering Plan(NANP).
1718      *
1719      * The passed-in dialStr should only contain the valid format as described below,
1720      * 1) the 1st character in the dialStr should be one of the really dialable
1721      *    characters listed below
1722      *    ISO-LATIN characters 0-9, *, # , +
1723      * 2) the dialStr should already strip out the separator characters,
1724      *    every character in the dialStr should be one of the non separator characters
1725      *    listed below
1726      *    ISO-LATIN characters 0-9, *, # , +, WILD, WAIT, PAUSE
1727      *
1728      * Otherwise, this function returns the dial string passed in
1729      *
1730      * @param dialStr the original dial string
1731      * @return the converted dial string if the current/default countries belong to NANP,
1732      * and if there is the "+" in the original dial string. Otherwise, the original dial
1733      * string returns.
1734      *
1735      * This API is for CDMA only
1736      *
1737      * @hide TODO: pending API Council approval
1738      */
cdmaCheckAndProcessPlusCode(String dialStr)1739     public static String cdmaCheckAndProcessPlusCode(String dialStr) {
1740         if (!TextUtils.isEmpty(dialStr)) {
1741             if (isReallyDialable(dialStr.charAt(0)) &&
1742                 isNonSeparator(dialStr)) {
1743                 String currIso = SystemProperties.get(PROPERTY_OPERATOR_ISO_COUNTRY, "");
1744                 String defaultIso = SystemProperties.get(PROPERTY_ICC_OPERATOR_ISO_COUNTRY, "");
1745                 if (!TextUtils.isEmpty(currIso) && !TextUtils.isEmpty(defaultIso)) {
1746                     return cdmaCheckAndProcessPlusCodeByNumberFormat(dialStr,
1747                             getFormatTypeFromCountryCode(currIso),
1748                             getFormatTypeFromCountryCode(defaultIso));
1749                 }
1750             }
1751         }
1752         return dialStr;
1753     }
1754 
1755     /**
1756      * This function should be called from checkAndProcessPlusCode only
1757      * And it is used for test purpose also.
1758      *
1759      * It checks the dial string by looping through the network portion,
1760      * post dial portion 1, post dial porting 2, etc. If there is any
1761      * plus sign, then process the plus sign.
1762      * Currently, this function supports the plus sign conversion within NANP only.
1763      * Specifically, it handles the plus sign in the following ways:
1764      * 1)+1NANP,remove +, e.g.
1765      *   +18475797000 is converted to 18475797000,
1766      * 2)+NANP or +non-NANP Numbers,replace + with the current NANP IDP, e.g,
1767      *   +8475797000 is converted to 0118475797000,
1768      *   +11875767800 is converted to 01111875767800
1769      * 3)+1NANP in post dial string(s), e.g.
1770      *   8475797000;+18475231753 is converted to 8475797000;18475231753
1771      *
1772      *
1773      * @param dialStr the original dial string
1774      * @param currFormat the numbering system of the current country that the phone is camped on
1775      * @param defaultFormat the numbering system of the country that the phone is activated on
1776      * @return the converted dial string if the current/default countries belong to NANP,
1777      * and if there is the "+" in the original dial string. Otherwise, the original dial
1778      * string returns.
1779      *
1780      * @hide
1781      */
1782     public static String
cdmaCheckAndProcessPlusCodeByNumberFormat(String dialStr,int currFormat,int defaultFormat)1783     cdmaCheckAndProcessPlusCodeByNumberFormat(String dialStr,int currFormat,int defaultFormat) {
1784         String retStr = dialStr;
1785 
1786         // Checks if the plus sign character is in the passed-in dial string
1787         if (dialStr != null &&
1788             dialStr.lastIndexOf(PLUS_SIGN_STRING) != -1) {
1789             // Format the string based on the rules for the country the number is from,
1790             // and the current country the phone is camped on.
1791             if ((currFormat == defaultFormat) && (currFormat == FORMAT_NANP)) {
1792                 // Handle case where default and current telephone numbering plans are NANP.
1793                 String postDialStr = null;
1794                 String tempDialStr = dialStr;
1795 
1796                 // Sets the retStr to null since the conversion will be performed below.
1797                 retStr = null;
1798                 if (DBG) log("checkAndProcessPlusCode,dialStr=" + dialStr);
1799                 // This routine is to process the plus sign in the dial string by loop through
1800                 // the network portion, post dial portion 1, post dial portion 2... etc. if
1801                 // applied
1802                 do {
1803                     String networkDialStr;
1804                     networkDialStr = extractNetworkPortion(tempDialStr);
1805                     // Handles the conversion within NANP
1806                     networkDialStr = processPlusCodeWithinNanp(networkDialStr);
1807 
1808                     // Concatenates the string that is converted from network portion
1809                     if (!TextUtils.isEmpty(networkDialStr)) {
1810                         if (retStr == null) {
1811                             retStr = networkDialStr;
1812                         } else {
1813                             retStr = retStr.concat(networkDialStr);
1814                         }
1815                     } else {
1816                         // This should never happen since we checked the if dialStr is null
1817                         // and if it contains the plus sign in the beginning of this function.
1818                         // The plus sign is part of the network portion.
1819                         Log.e("checkAndProcessPlusCode: null newDialStr", networkDialStr);
1820                         return dialStr;
1821                     }
1822                     postDialStr = extractPostDialPortion(tempDialStr);
1823                     if (!TextUtils.isEmpty(postDialStr)) {
1824                         int dialableIndex = findDialableIndexFromPostDialStr(postDialStr);
1825 
1826                         // dialableIndex should always be greater than 0
1827                         if (dialableIndex >= 1) {
1828                             retStr = appendPwCharBackToOrigDialStr(dialableIndex,
1829                                      retStr,postDialStr);
1830                             // Skips the P/W character, extracts the dialable portion
1831                             tempDialStr = postDialStr.substring(dialableIndex);
1832                         } else {
1833                             // Non-dialable character such as P/W should not be at the end of
1834                             // the dial string after P/W processing in CdmaConnection.java
1835                             // Set the postDialStr to "" to break out of the loop
1836                             if (dialableIndex < 0) {
1837                                 postDialStr = "";
1838                             }
1839                             Log.e("wrong postDialStr=", postDialStr);
1840                         }
1841                     }
1842                     if (DBG) log("checkAndProcessPlusCode,postDialStr=" + postDialStr);
1843                 } while (!TextUtils.isEmpty(postDialStr) && !TextUtils.isEmpty(tempDialStr));
1844             } else {
1845                 // TODO: Support NANP international conversion and other telephone numbering plans.
1846                 // Currently the phone is never used in non-NANP system, so return the original
1847                 // dial string.
1848                 Log.e("checkAndProcessPlusCode:non-NANP not supported", dialStr);
1849             }
1850         }
1851         return retStr;
1852      }
1853 
1854     // This function gets the default international dialing prefix
getDefaultIdp( )1855     private static String getDefaultIdp( ) {
1856         String ps = null;
1857         SystemProperties.get(PROPERTY_IDP_STRING, ps);
1858         if (TextUtils.isEmpty(ps)) {
1859             ps = NANP_IDP_STRING;
1860         }
1861         return ps;
1862     }
1863 
isTwoToNine(char c)1864     private static boolean isTwoToNine (char c) {
1865         if (c >= '2' && c <= '9') {
1866             return true;
1867         } else {
1868             return false;
1869         }
1870     }
1871 
getFormatTypeFromCountryCode(String country)1872     private static int getFormatTypeFromCountryCode (String country) {
1873         // Check for the NANP countries
1874         int length = NANP_COUNTRIES.length;
1875         for (int i = 0; i < length; i++) {
1876             if (NANP_COUNTRIES[i].compareToIgnoreCase(country) == 0) {
1877                 return FORMAT_NANP;
1878             }
1879         }
1880         if ("jp".compareToIgnoreCase(country) == 0) {
1881             return FORMAT_JAPAN;
1882         }
1883         return FORMAT_UNKNOWN;
1884     }
1885 
1886     /**
1887      * This function checks if the passed in string conforms to the NANP format
1888      * i.e. NXX-NXX-XXXX, N is any digit 2-9 and X is any digit 0-9
1889      */
isNanp(String dialStr)1890     private static boolean isNanp (String dialStr) {
1891         boolean retVal = false;
1892         if (dialStr != null) {
1893             if (dialStr.length() == NANP_LENGTH) {
1894                 if (isTwoToNine(dialStr.charAt(0)) &&
1895                     isTwoToNine(dialStr.charAt(3))) {
1896                     retVal = true;
1897                     for (int i=1; i<NANP_LENGTH; i++ ) {
1898                         char c=dialStr.charAt(i);
1899                         if (!PhoneNumberUtils.isISODigit(c)) {
1900                             retVal = false;
1901                             break;
1902                         }
1903                     }
1904                 }
1905             }
1906         } else {
1907             Log.e("isNanp: null dialStr passed in", dialStr);
1908         }
1909         return retVal;
1910     }
1911 
1912    /**
1913     * This function checks if the passed in string conforms to 1-NANP format
1914     */
isOneNanp(String dialStr)1915     private static boolean isOneNanp(String dialStr) {
1916         boolean retVal = false;
1917         if (dialStr != null) {
1918             String newDialStr = dialStr.substring(1);
1919             if ((dialStr.charAt(0) == '1') && isNanp(newDialStr)) {
1920                 retVal = true;
1921             }
1922         } else {
1923             Log.e("isOneNanp: null dialStr passed in", dialStr);
1924         }
1925         return retVal;
1926     }
1927 
1928     /**
1929      * Determines if the specified number is actually a URI
1930      * (i.e. a SIP address) rather than a regular PSTN phone number,
1931      * based on whether or not the number contains an "@" character.
1932      *
1933      * @hide
1934      * @param number
1935      * @return true if number contains @
1936      */
isUriNumber(String number)1937     public static boolean isUriNumber(String number) {
1938         // Note we allow either "@" or "%40" to indicate a URI, in case
1939         // the passed-in string is URI-escaped.  (Neither "@" nor "%40"
1940         // will ever be found in a legal PSTN number.)
1941         return number != null && (number.contains("@") || number.contains("%40"));
1942     }
1943 
1944     /**
1945      * This function handles the plus code conversion within NANP CDMA network
1946      * If the number format is
1947      * 1)+1NANP,remove +,
1948      * 2)other than +1NANP, any + numbers,replace + with the current IDP
1949      */
processPlusCodeWithinNanp(String networkDialStr)1950     private static String processPlusCodeWithinNanp(String networkDialStr) {
1951         String retStr = networkDialStr;
1952 
1953         if (DBG) log("processPlusCodeWithinNanp,networkDialStr=" + networkDialStr);
1954         // If there is a plus sign at the beginning of the dial string,
1955         // Convert the plus sign to the default IDP since it's an international number
1956         if (networkDialStr != null &&
1957             networkDialStr.charAt(0) == PLUS_SIGN_CHAR &&
1958             networkDialStr.length() > 1) {
1959             String newStr = networkDialStr.substring(1);
1960             if (isOneNanp(newStr)) {
1961                 // Remove the leading plus sign
1962                 retStr = newStr;
1963              } else {
1964                  String idpStr = getDefaultIdp();
1965                  // Replaces the plus sign with the default IDP
1966                  retStr = networkDialStr.replaceFirst("[+]", idpStr);
1967             }
1968         }
1969         if (DBG) log("processPlusCodeWithinNanp,retStr=" + retStr);
1970         return retStr;
1971     }
1972 
1973     // This function finds the index of the dialable character(s)
1974     // in the post dial string
findDialableIndexFromPostDialStr(String postDialStr)1975     private static int findDialableIndexFromPostDialStr(String postDialStr) {
1976         for (int index = 0;index < postDialStr.length();index++) {
1977              char c = postDialStr.charAt(index);
1978              if (isReallyDialable(c)) {
1979                 return index;
1980              }
1981         }
1982         return -1;
1983     }
1984 
1985     // This function appends the non-dialable P/W character to the original
1986     // dial string based on the dialable index passed in
1987     private static String
appendPwCharBackToOrigDialStr(int dialableIndex,String origStr, String dialStr)1988     appendPwCharBackToOrigDialStr(int dialableIndex,String origStr, String dialStr) {
1989         String retStr;
1990 
1991         // There is only 1 P/W character before the dialable characters
1992         if (dialableIndex == 1) {
1993             StringBuilder ret = new StringBuilder(origStr);
1994             ret = ret.append(dialStr.charAt(0));
1995             retStr = ret.toString();
1996         } else {
1997             // It means more than 1 P/W characters in the post dial string,
1998             // appends to retStr
1999             String nonDigitStr = dialStr.substring(0,dialableIndex);
2000             retStr = origStr.concat(nonDigitStr);
2001         }
2002         return retStr;
2003     }
2004 
2005     //===== Beginning of utility methods used in compareLoosely() =====
2006 
2007     /**
2008      * Phone numbers are stored in "lookup" form in the database
2009      * as reversed strings to allow for caller ID lookup
2010      *
2011      * This method takes a phone number and makes a valid SQL "LIKE"
2012      * string that will match the lookup form
2013      *
2014      */
2015     /** all of a up to len must be an international prefix or
2016      *  separators/non-dialing digits
2017      */
2018     private static boolean
matchIntlPrefix(String a, int len)2019     matchIntlPrefix(String a, int len) {
2020         /* '([^0-9*#+pwn]\+[^0-9*#+pwn] | [^0-9*#+pwn]0(0|11)[^0-9*#+pwn] )$' */
2021         /*        0       1                           2 3 45               */
2022 
2023         int state = 0;
2024         for (int i = 0 ; i < len ; i++) {
2025             char c = a.charAt(i);
2026 
2027             switch (state) {
2028                 case 0:
2029                     if      (c == '+') state = 1;
2030                     else if (c == '0') state = 2;
2031                     else if (isNonSeparator(c)) return false;
2032                 break;
2033 
2034                 case 2:
2035                     if      (c == '0') state = 3;
2036                     else if (c == '1') state = 4;
2037                     else if (isNonSeparator(c)) return false;
2038                 break;
2039 
2040                 case 4:
2041                     if      (c == '1') state = 5;
2042                     else if (isNonSeparator(c)) return false;
2043                 break;
2044 
2045                 default:
2046                     if (isNonSeparator(c)) return false;
2047                 break;
2048 
2049             }
2050         }
2051 
2052         return state == 1 || state == 3 || state == 5;
2053     }
2054 
2055     /** all of 'a' up to len must be a (+|00|011)country code)
2056      *  We're fast and loose with the country code. Any \d{1,3} matches */
2057     private static boolean
matchIntlPrefixAndCC(String a, int len)2058     matchIntlPrefixAndCC(String a, int len) {
2059         /*  [^0-9*#+pwn]*(\+|0(0|11)\d\d?\d? [^0-9*#+pwn] $ */
2060         /*      0          1 2 3 45  6 7  8                 */
2061 
2062         int state = 0;
2063         for (int i = 0 ; i < len ; i++ ) {
2064             char c = a.charAt(i);
2065 
2066             switch (state) {
2067                 case 0:
2068                     if      (c == '+') state = 1;
2069                     else if (c == '0') state = 2;
2070                     else if (isNonSeparator(c)) return false;
2071                 break;
2072 
2073                 case 2:
2074                     if      (c == '0') state = 3;
2075                     else if (c == '1') state = 4;
2076                     else if (isNonSeparator(c)) return false;
2077                 break;
2078 
2079                 case 4:
2080                     if      (c == '1') state = 5;
2081                     else if (isNonSeparator(c)) return false;
2082                 break;
2083 
2084                 case 1:
2085                 case 3:
2086                 case 5:
2087                     if      (isISODigit(c)) state = 6;
2088                     else if (isNonSeparator(c)) return false;
2089                 break;
2090 
2091                 case 6:
2092                 case 7:
2093                     if      (isISODigit(c)) state++;
2094                     else if (isNonSeparator(c)) return false;
2095                 break;
2096 
2097                 default:
2098                     if (isNonSeparator(c)) return false;
2099             }
2100         }
2101 
2102         return state == 6 || state == 7 || state == 8;
2103     }
2104 
2105     /** all of 'a' up to len must match non-US trunk prefix ('0') */
2106     private static boolean
matchTrunkPrefix(String a, int len)2107     matchTrunkPrefix(String a, int len) {
2108         boolean found;
2109 
2110         found = false;
2111 
2112         for (int i = 0 ; i < len ; i++) {
2113             char c = a.charAt(i);
2114 
2115             if (c == '0' && !found) {
2116                 found = true;
2117             } else if (isNonSeparator(c)) {
2118                 return false;
2119             }
2120         }
2121 
2122         return found;
2123     }
2124 
2125     //===== End of utility methods used only in compareLoosely() =====
2126 
2127     //===== Beginning of utility methods used only in compareStrictly() ====
2128 
2129     /*
2130      * If true, the number is country calling code.
2131      */
2132     private static final boolean COUNTRY_CALLING_CALL[] = {
2133         true, true, false, false, false, false, false, true, false, false,
2134         false, false, false, false, false, false, false, false, false, false,
2135         true, false, false, false, false, false, false, true, true, false,
2136         true, true, true, true, true, false, true, false, false, true,
2137         true, false, false, true, true, true, true, true, true, true,
2138         false, true, true, true, true, true, true, true, true, false,
2139         true, true, true, true, true, true, true, false, false, false,
2140         false, false, false, false, false, false, false, false, false, false,
2141         false, true, true, true, true, false, true, false, false, true,
2142         true, true, true, true, true, true, false, false, true, false,
2143     };
2144     private static final int CCC_LENGTH = COUNTRY_CALLING_CALL.length;
2145 
2146     /**
2147      * @return true when input is valid Country Calling Code.
2148      */
isCountryCallingCode(int countryCallingCodeCandidate)2149     private static boolean isCountryCallingCode(int countryCallingCodeCandidate) {
2150         return countryCallingCodeCandidate > 0 && countryCallingCodeCandidate < CCC_LENGTH &&
2151                 COUNTRY_CALLING_CALL[countryCallingCodeCandidate];
2152     }
2153 
2154     /**
2155      * Returns integer corresponding to the input if input "ch" is
2156      * ISO-LATIN characters 0-9.
2157      * Returns -1 otherwise
2158      */
tryGetISODigit(char ch)2159     private static int tryGetISODigit(char ch) {
2160         if ('0' <= ch && ch <= '9') {
2161             return ch - '0';
2162         } else {
2163             return -1;
2164         }
2165     }
2166 
2167     private static class CountryCallingCodeAndNewIndex {
2168         public final int countryCallingCode;
2169         public final int newIndex;
CountryCallingCodeAndNewIndex(int countryCode, int newIndex)2170         public CountryCallingCodeAndNewIndex(int countryCode, int newIndex) {
2171             this.countryCallingCode = countryCode;
2172             this.newIndex = newIndex;
2173         }
2174     }
2175 
2176     /*
2177      * Note that this function does not strictly care the country calling code with
2178      * 3 length (like Morocco: +212), assuming it is enough to use the first two
2179      * digit to compare two phone numbers.
2180      */
tryGetCountryCallingCodeAndNewIndex( String str, boolean acceptThailandCase)2181     private static CountryCallingCodeAndNewIndex tryGetCountryCallingCodeAndNewIndex(
2182         String str, boolean acceptThailandCase) {
2183         // Rough regexp:
2184         //  ^[^0-9*#+]*((\+|0(0|11)\d\d?|166) [^0-9*#+] $
2185         //         0        1 2 3 45  6 7  89
2186         //
2187         // In all the states, this function ignores separator characters.
2188         // "166" is the special case for the call from Thailand to the US. Uguu!
2189         int state = 0;
2190         int ccc = 0;
2191         final int length = str.length();
2192         for (int i = 0 ; i < length ; i++ ) {
2193             char ch = str.charAt(i);
2194             switch (state) {
2195                 case 0:
2196                     if      (ch == '+') state = 1;
2197                     else if (ch == '0') state = 2;
2198                     else if (ch == '1') {
2199                         if (acceptThailandCase) {
2200                             state = 8;
2201                         } else {
2202                             return null;
2203                         }
2204                     } else if (isDialable(ch)) {
2205                         return null;
2206                     }
2207                 break;
2208 
2209                 case 2:
2210                     if      (ch == '0') state = 3;
2211                     else if (ch == '1') state = 4;
2212                     else if (isDialable(ch)) {
2213                         return null;
2214                     }
2215                 break;
2216 
2217                 case 4:
2218                     if      (ch == '1') state = 5;
2219                     else if (isDialable(ch)) {
2220                         return null;
2221                     }
2222                 break;
2223 
2224                 case 1:
2225                 case 3:
2226                 case 5:
2227                 case 6:
2228                 case 7:
2229                     {
2230                         int ret = tryGetISODigit(ch);
2231                         if (ret > 0) {
2232                             ccc = ccc * 10 + ret;
2233                             if (ccc >= 100 || isCountryCallingCode(ccc)) {
2234                                 return new CountryCallingCodeAndNewIndex(ccc, i + 1);
2235                             }
2236                             if (state == 1 || state == 3 || state == 5) {
2237                                 state = 6;
2238                             } else {
2239                                 state++;
2240                             }
2241                         } else if (isDialable(ch)) {
2242                             return null;
2243                         }
2244                     }
2245                     break;
2246                 case 8:
2247                     if (ch == '6') state = 9;
2248                     else if (isDialable(ch)) {
2249                         return null;
2250                     }
2251                     break;
2252                 case 9:
2253                     if (ch == '6') {
2254                         return new CountryCallingCodeAndNewIndex(66, i + 1);
2255                     } else {
2256                         return null;
2257                     }
2258                 default:
2259                     return null;
2260             }
2261         }
2262 
2263         return null;
2264     }
2265 
2266     /**
2267      * Currently this function simply ignore the first digit assuming it is
2268      * trunk prefix. Actually trunk prefix is different in each country.
2269      *
2270      * e.g.
2271      * "+79161234567" equals "89161234567" (Russian trunk digit is 8)
2272      * "+33123456789" equals "0123456789" (French trunk digit is 0)
2273      *
2274      */
tryGetTrunkPrefixOmittedIndex(String str, int currentIndex)2275     private static int tryGetTrunkPrefixOmittedIndex(String str, int currentIndex) {
2276         int length = str.length();
2277         for (int i = currentIndex ; i < length ; i++) {
2278             final char ch = str.charAt(i);
2279             if (tryGetISODigit(ch) >= 0) {
2280                 return i + 1;
2281             } else if (isDialable(ch)) {
2282                 return -1;
2283             }
2284         }
2285         return -1;
2286     }
2287 
2288     /**
2289      * Return true if the prefix of "str" is "ignorable". Here, "ignorable" means
2290      * that "str" has only one digit and separator characters. The one digit is
2291      * assumed to be trunk prefix.
2292      */
checkPrefixIsIgnorable(final String str, int forwardIndex, int backwardIndex)2293     private static boolean checkPrefixIsIgnorable(final String str,
2294             int forwardIndex, int backwardIndex) {
2295         boolean trunk_prefix_was_read = false;
2296         while (backwardIndex >= forwardIndex) {
2297             if (tryGetISODigit(str.charAt(backwardIndex)) >= 0) {
2298                 if (trunk_prefix_was_read) {
2299                     // More than one digit appeared, meaning that "a" and "b"
2300                     // is different.
2301                     return false;
2302                 } else {
2303                     // Ignore just one digit, assuming it is trunk prefix.
2304                     trunk_prefix_was_read = true;
2305                 }
2306             } else if (isDialable(str.charAt(backwardIndex))) {
2307                 // Trunk prefix is a digit, not "*", "#"...
2308                 return false;
2309             }
2310             backwardIndex--;
2311         }
2312 
2313         return true;
2314     }
2315 
2316     //==== End of utility methods used only in compareStrictly() =====
2317 }
2318