• 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 import com.android.i18n.phonenumbers.ShortNumberUtil;
24 
25 import android.content.Context;
26 import android.content.Intent;
27 import android.database.Cursor;
28 import android.location.CountryDetector;
29 import android.net.Uri;
30 import android.os.SystemProperties;
31 import android.provider.Contacts;
32 import android.provider.ContactsContract;
33 import android.text.Editable;
34 import android.text.Spannable;
35 import android.text.SpannableStringBuilder;
36 import android.text.TextUtils;
37 import android.text.style.TtsSpan;
38 import android.util.SparseIntArray;
39 
40 import static com.android.internal.telephony.TelephonyProperties.PROPERTY_OPERATOR_IDP_STRING;
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 
155         if (uri == null) {
156             return null;
157         }
158 
159         String scheme = uri.getScheme();
160 
161         if (scheme.equals("tel") || scheme.equals("sip")) {
162             return uri.getSchemeSpecificPart();
163         }
164 
165         if (context == null) {
166             return null;
167         }
168 
169         String type = intent.resolveType(context);
170         String phoneColumn = null;
171 
172         // Correctly read out the phone entry based on requested provider
173         final String authority = uri.getAuthority();
174         if (Contacts.AUTHORITY.equals(authority)) {
175             phoneColumn = Contacts.People.Phones.NUMBER;
176         } else if (ContactsContract.AUTHORITY.equals(authority)) {
177             phoneColumn = ContactsContract.CommonDataKinds.Phone.NUMBER;
178         }
179 
180         Cursor c = null;
181         try {
182             c = context.getContentResolver().query(uri, new String[] { phoneColumn },
183                     null, null, null);
184             if (c != null) {
185                 if (c.moveToFirst()) {
186                     number = c.getString(c.getColumnIndex(phoneColumn));
187                 }
188             }
189         } catch (RuntimeException e) {
190             Rlog.e(LOG_TAG, "Error getting phone number.", e);
191         } finally {
192             if (c != null) {
193                 c.close();
194             }
195         }
196 
197         return number;
198     }
199 
200     /** Extracts the network address portion and canonicalizes
201      *  (filters out separators.)
202      *  Network address portion is everything up to DTMF control digit
203      *  separators (pause or wait), but without non-dialable characters.
204      *
205      *  Please note that the GSM wild character is allowed in the result.
206      *  This must be resolved before dialing.
207      *
208      *  Returns null if phoneNumber == null
209      */
210     public static String
extractNetworkPortion(String phoneNumber)211     extractNetworkPortion(String phoneNumber) {
212         if (phoneNumber == null) {
213             return null;
214         }
215 
216         int len = phoneNumber.length();
217         StringBuilder ret = new StringBuilder(len);
218 
219         for (int i = 0; i < len; i++) {
220             char c = phoneNumber.charAt(i);
221             // Character.digit() supports ASCII and Unicode digits (fullwidth, Arabic-Indic, etc.)
222             int digit = Character.digit(c, 10);
223             if (digit != -1) {
224                 ret.append(digit);
225             } else if (c == '+') {
226                 // Allow '+' as first character or after CLIR MMI prefix
227                 String prefix = ret.toString();
228                 if (prefix.length() == 0 || prefix.equals(CLIR_ON) || prefix.equals(CLIR_OFF)) {
229                     ret.append(c);
230                 }
231             } else if (isDialable(c)) {
232                 ret.append(c);
233             } else if (isStartsPostDial (c)) {
234                 break;
235             }
236         }
237 
238         return ret.toString();
239     }
240 
241     /**
242      * Extracts the network address portion and canonicalize.
243      *
244      * This function is equivalent to extractNetworkPortion(), except
245      * for allowing the PLUS character to occur at arbitrary positions
246      * in the address portion, not just the first position.
247      *
248      * @hide
249      */
extractNetworkPortionAlt(String phoneNumber)250     public static String extractNetworkPortionAlt(String phoneNumber) {
251         if (phoneNumber == null) {
252             return null;
253         }
254 
255         int len = phoneNumber.length();
256         StringBuilder ret = new StringBuilder(len);
257         boolean haveSeenPlus = false;
258 
259         for (int i = 0; i < len; i++) {
260             char c = phoneNumber.charAt(i);
261             if (c == '+') {
262                 if (haveSeenPlus) {
263                     continue;
264                 }
265                 haveSeenPlus = true;
266             }
267             if (isDialable(c)) {
268                 ret.append(c);
269             } else if (isStartsPostDial (c)) {
270                 break;
271             }
272         }
273 
274         return ret.toString();
275     }
276 
277     /**
278      * Strips separators from a phone number string.
279      * @param phoneNumber phone number to strip.
280      * @return phone string stripped of separators.
281      */
stripSeparators(String phoneNumber)282     public static String stripSeparators(String phoneNumber) {
283         if (phoneNumber == null) {
284             return null;
285         }
286         int len = phoneNumber.length();
287         StringBuilder ret = new StringBuilder(len);
288 
289         for (int i = 0; i < len; i++) {
290             char c = phoneNumber.charAt(i);
291             // Character.digit() supports ASCII and Unicode digits (fullwidth, Arabic-Indic, etc.)
292             int digit = Character.digit(c, 10);
293             if (digit != -1) {
294                 ret.append(digit);
295             } else if (isNonSeparator(c)) {
296                 ret.append(c);
297             }
298         }
299 
300         return ret.toString();
301     }
302 
303     /**
304      * Translates keypad letters to actual digits (e.g. 1-800-GOOG-411 will
305      * become 1-800-4664-411), and then strips all separators (e.g. 1-800-4664-411 will become
306      * 18004664411).
307      *
308      * @see #convertKeypadLettersToDigits(String)
309      * @see #stripSeparators(String)
310      *
311      * @hide
312      */
convertAndStrip(String phoneNumber)313     public static String convertAndStrip(String phoneNumber) {
314         return stripSeparators(convertKeypadLettersToDigits(phoneNumber));
315     }
316 
317     /**
318      * Converts pause and tonewait pause characters
319      * to Android representation.
320      * RFC 3601 says pause is 'p' and tonewait is 'w'.
321      * @hide
322      */
convertPreDial(String phoneNumber)323     public static String convertPreDial(String phoneNumber) {
324         if (phoneNumber == null) {
325             return null;
326         }
327         int len = phoneNumber.length();
328         StringBuilder ret = new StringBuilder(len);
329 
330         for (int i = 0; i < len; i++) {
331             char c = phoneNumber.charAt(i);
332 
333             if (isPause(c)) {
334                 c = PAUSE;
335             } else if (isToneWait(c)) {
336                 c = WAIT;
337             }
338             ret.append(c);
339         }
340         return ret.toString();
341     }
342 
343     /** or -1 if both are negative */
344     static private int
minPositive(int a, int b)345     minPositive (int a, int b) {
346         if (a >= 0 && b >= 0) {
347             return (a < b) ? a : b;
348         } else if (a >= 0) { /* && b < 0 */
349             return a;
350         } else if (b >= 0) { /* && a < 0 */
351             return b;
352         } else { /* a < 0 && b < 0 */
353             return -1;
354         }
355     }
356 
log(String msg)357     private static void log(String msg) {
358         Rlog.d(LOG_TAG, msg);
359     }
360     /** index of the last character of the network portion
361      *  (eg anything after is a post-dial string)
362      */
363     static private int
indexOfLastNetworkChar(String a)364     indexOfLastNetworkChar(String a) {
365         int pIndex, wIndex;
366         int origLength;
367         int trimIndex;
368 
369         origLength = a.length();
370 
371         pIndex = a.indexOf(PAUSE);
372         wIndex = a.indexOf(WAIT);
373 
374         trimIndex = minPositive(pIndex, wIndex);
375 
376         if (trimIndex < 0) {
377             return origLength - 1;
378         } else {
379             return trimIndex - 1;
380         }
381     }
382 
383     /**
384      * Extracts the post-dial sequence of DTMF control digits, pauses, and
385      * waits. Strips separators. This string may be empty, but will not be null
386      * unless phoneNumber == null.
387      *
388      * Returns null if phoneNumber == null
389      */
390 
391     public static String
extractPostDialPortion(String phoneNumber)392     extractPostDialPortion(String phoneNumber) {
393         if (phoneNumber == null) return null;
394 
395         int trimIndex;
396         StringBuilder ret = new StringBuilder();
397 
398         trimIndex = indexOfLastNetworkChar (phoneNumber);
399 
400         for (int i = trimIndex + 1, s = phoneNumber.length()
401                 ; i < s; i++
402         ) {
403             char c = phoneNumber.charAt(i);
404             if (isNonSeparator(c)) {
405                 ret.append(c);
406             }
407         }
408 
409         return ret.toString();
410     }
411 
412     /**
413      * Compare phone numbers a and b, return true if they're identical enough for caller ID purposes.
414      */
compare(String a, String b)415     public static boolean compare(String a, String b) {
416         // We've used loose comparation at least Eclair, which may change in the future.
417 
418         return compare(a, b, false);
419     }
420 
421     /**
422      * Compare phone numbers a and b, and return true if they're identical
423      * enough for caller ID purposes. Checks a resource to determine whether
424      * to use a strict or loose comparison algorithm.
425      */
compare(Context context, String a, String b)426     public static boolean compare(Context context, String a, String b) {
427         boolean useStrict = context.getResources().getBoolean(
428                com.android.internal.R.bool.config_use_strict_phone_number_comparation);
429         return compare(a, b, useStrict);
430     }
431 
432     /**
433      * @hide only for testing.
434      */
compare(String a, String b, boolean useStrictComparation)435     public static boolean compare(String a, String b, boolean useStrictComparation) {
436         return (useStrictComparation ? compareStrictly(a, b) : compareLoosely(a, b));
437     }
438 
439     /**
440      * Compare phone numbers a and b, return true if they're identical
441      * enough for caller ID purposes.
442      *
443      * - Compares from right to left
444      * - requires MIN_MATCH (7) characters to match
445      * - handles common trunk prefixes and international prefixes
446      *   (basically, everything except the Russian trunk prefix)
447      *
448      * Note that this method does not return false even when the two phone numbers
449      * are not exactly same; rather; we can call this method "similar()", not "equals()".
450      *
451      * @hide
452      */
453     public static boolean
compareLoosely(String a, String b)454     compareLoosely(String a, String b) {
455         int ia, ib;
456         int matched;
457         int numNonDialableCharsInA = 0;
458         int numNonDialableCharsInB = 0;
459 
460         if (a == null || b == null) return a == b;
461 
462         if (a.length() == 0 || b.length() == 0) {
463             return false;
464         }
465 
466         ia = indexOfLastNetworkChar (a);
467         ib = indexOfLastNetworkChar (b);
468         matched = 0;
469 
470         while (ia >= 0 && ib >=0) {
471             char ca, cb;
472             boolean skipCmp = false;
473 
474             ca = a.charAt(ia);
475 
476             if (!isDialable(ca)) {
477                 ia--;
478                 skipCmp = true;
479                 numNonDialableCharsInA++;
480             }
481 
482             cb = b.charAt(ib);
483 
484             if (!isDialable(cb)) {
485                 ib--;
486                 skipCmp = true;
487                 numNonDialableCharsInB++;
488             }
489 
490             if (!skipCmp) {
491                 if (cb != ca && ca != WILD && cb != WILD) {
492                     break;
493                 }
494                 ia--; ib--; matched++;
495             }
496         }
497 
498         if (matched < MIN_MATCH) {
499             int effectiveALen = a.length() - numNonDialableCharsInA;
500             int effectiveBLen = b.length() - numNonDialableCharsInB;
501 
502 
503             // if the number of dialable chars in a and b match, but the matched chars < MIN_MATCH,
504             // treat them as equal (i.e. 404-04 and 40404)
505             if (effectiveALen == effectiveBLen && effectiveALen == matched) {
506                 return true;
507             }
508 
509             return false;
510         }
511 
512         // At least one string has matched completely;
513         if (matched >= MIN_MATCH && (ia < 0 || ib < 0)) {
514             return true;
515         }
516 
517         /*
518          * Now, what remains must be one of the following for a
519          * match:
520          *
521          *  - a '+' on one and a '00' or a '011' on the other
522          *  - a '0' on one and a (+,00)<country code> on the other
523          *     (for this, a '0' and a '00' prefix would have succeeded above)
524          */
525 
526         if (matchIntlPrefix(a, ia + 1)
527             && matchIntlPrefix (b, ib +1)
528         ) {
529             return true;
530         }
531 
532         if (matchTrunkPrefix(a, ia + 1)
533             && matchIntlPrefixAndCC(b, ib +1)
534         ) {
535             return true;
536         }
537 
538         if (matchTrunkPrefix(b, ib + 1)
539             && matchIntlPrefixAndCC(a, ia +1)
540         ) {
541             return true;
542         }
543 
544         return false;
545     }
546 
547     /**
548      * @hide
549      */
550     public static boolean
compareStrictly(String a, String b)551     compareStrictly(String a, String b) {
552         return compareStrictly(a, b, true);
553     }
554 
555     /**
556      * @hide
557      */
558     public static boolean
compareStrictly(String a, String b, boolean acceptInvalidCCCPrefix)559     compareStrictly(String a, String b, boolean acceptInvalidCCCPrefix) {
560         if (a == null || b == null) {
561             return a == b;
562         } else if (a.length() == 0 && b.length() == 0) {
563             return false;
564         }
565 
566         int forwardIndexA = 0;
567         int forwardIndexB = 0;
568 
569         CountryCallingCodeAndNewIndex cccA =
570             tryGetCountryCallingCodeAndNewIndex(a, acceptInvalidCCCPrefix);
571         CountryCallingCodeAndNewIndex cccB =
572             tryGetCountryCallingCodeAndNewIndex(b, acceptInvalidCCCPrefix);
573         boolean bothHasCountryCallingCode = false;
574         boolean okToIgnorePrefix = true;
575         boolean trunkPrefixIsOmittedA = false;
576         boolean trunkPrefixIsOmittedB = false;
577         if (cccA != null && cccB != null) {
578             if (cccA.countryCallingCode != cccB.countryCallingCode) {
579                 // Different Country Calling Code. Must be different phone number.
580                 return false;
581             }
582             // When both have ccc, do not ignore trunk prefix. Without this,
583             // "+81123123" becomes same as "+810123123" (+81 == Japan)
584             okToIgnorePrefix = false;
585             bothHasCountryCallingCode = true;
586             forwardIndexA = cccA.newIndex;
587             forwardIndexB = cccB.newIndex;
588         } else if (cccA == null && cccB == null) {
589             // When both do not have ccc, do not ignore trunk prefix. Without this,
590             // "123123" becomes same as "0123123"
591             okToIgnorePrefix = false;
592         } else {
593             if (cccA != null) {
594                 forwardIndexA = cccA.newIndex;
595             } else {
596                 int tmp = tryGetTrunkPrefixOmittedIndex(b, 0);
597                 if (tmp >= 0) {
598                     forwardIndexA = tmp;
599                     trunkPrefixIsOmittedA = true;
600                 }
601             }
602             if (cccB != null) {
603                 forwardIndexB = cccB.newIndex;
604             } else {
605                 int tmp = tryGetTrunkPrefixOmittedIndex(b, 0);
606                 if (tmp >= 0) {
607                     forwardIndexB = tmp;
608                     trunkPrefixIsOmittedB = true;
609                 }
610             }
611         }
612 
613         int backwardIndexA = a.length() - 1;
614         int backwardIndexB = b.length() - 1;
615         while (backwardIndexA >= forwardIndexA && backwardIndexB >= forwardIndexB) {
616             boolean skip_compare = false;
617             final char chA = a.charAt(backwardIndexA);
618             final char chB = b.charAt(backwardIndexB);
619             if (isSeparator(chA)) {
620                 backwardIndexA--;
621                 skip_compare = true;
622             }
623             if (isSeparator(chB)) {
624                 backwardIndexB--;
625                 skip_compare = true;
626             }
627 
628             if (!skip_compare) {
629                 if (chA != chB) {
630                     return false;
631                 }
632                 backwardIndexA--;
633                 backwardIndexB--;
634             }
635         }
636 
637         if (okToIgnorePrefix) {
638             if ((trunkPrefixIsOmittedA && forwardIndexA <= backwardIndexA) ||
639                 !checkPrefixIsIgnorable(a, forwardIndexA, backwardIndexA)) {
640                 if (acceptInvalidCCCPrefix) {
641                     // Maybe the code handling the special case for Thailand makes the
642                     // result garbled, so disable the code and try again.
643                     // e.g. "16610001234" must equal to "6610001234", but with
644                     //      Thailand-case handling code, they become equal to each other.
645                     //
646                     // Note: we select simplicity rather than adding some complicated
647                     //       logic here for performance(like "checking whether remaining
648                     //       numbers are just 66 or not"), assuming inputs are small
649                     //       enough.
650                     return compare(a, b, false);
651                 } else {
652                     return false;
653                 }
654             }
655             if ((trunkPrefixIsOmittedB && forwardIndexB <= backwardIndexB) ||
656                 !checkPrefixIsIgnorable(b, forwardIndexA, backwardIndexB)) {
657                 if (acceptInvalidCCCPrefix) {
658                     return compare(a, b, false);
659                 } else {
660                     return false;
661                 }
662             }
663         } else {
664             // In the US, 1-650-555-1234 must be equal to 650-555-1234,
665             // while 090-1234-1234 must not be equal to 90-1234-1234 in Japan.
666             // This request exists just in US (with 1 trunk (NDD) prefix).
667             // In addition, "011 11 7005554141" must not equal to "+17005554141",
668             // while "011 1 7005554141" must equal to "+17005554141"
669             //
670             // In this comparison, we ignore the prefix '1' just once, when
671             // - at least either does not have CCC, or
672             // - the remaining non-separator number is 1
673             boolean maybeNamp = !bothHasCountryCallingCode;
674             while (backwardIndexA >= forwardIndexA) {
675                 final char chA = a.charAt(backwardIndexA);
676                 if (isDialable(chA)) {
677                     if (maybeNamp && tryGetISODigit(chA) == 1) {
678                         maybeNamp = false;
679                     } else {
680                         return false;
681                     }
682                 }
683                 backwardIndexA--;
684             }
685             while (backwardIndexB >= forwardIndexB) {
686                 final char chB = b.charAt(backwardIndexB);
687                 if (isDialable(chB)) {
688                     if (maybeNamp && tryGetISODigit(chB) == 1) {
689                         maybeNamp = false;
690                     } else {
691                         return false;
692                     }
693                 }
694                 backwardIndexB--;
695             }
696         }
697 
698         return true;
699     }
700 
701     /**
702      * Returns the rightmost MIN_MATCH (5) characters in the network portion
703      * in *reversed* order
704      *
705      * This can be used to do a database lookup against the column
706      * that stores getStrippedReversed()
707      *
708      * Returns null if phoneNumber == null
709      */
710     public static String
toCallerIDMinMatch(String phoneNumber)711     toCallerIDMinMatch(String phoneNumber) {
712         String np = extractNetworkPortionAlt(phoneNumber);
713         return internalGetStrippedReversed(np, MIN_MATCH);
714     }
715 
716     /**
717      * Returns the network portion reversed.
718      * This string is intended to go into an index column for a
719      * database lookup.
720      *
721      * Returns null if phoneNumber == null
722      */
723     public static String
getStrippedReversed(String phoneNumber)724     getStrippedReversed(String phoneNumber) {
725         String np = extractNetworkPortionAlt(phoneNumber);
726 
727         if (np == null) return null;
728 
729         return internalGetStrippedReversed(np, np.length());
730     }
731 
732     /**
733      * Returns the last numDigits of the reversed phone number
734      * Returns null if np == null
735      */
736     private static String
internalGetStrippedReversed(String np, int numDigits)737     internalGetStrippedReversed(String np, int numDigits) {
738         if (np == null) return null;
739 
740         StringBuilder ret = new StringBuilder(numDigits);
741         int length = np.length();
742 
743         for (int i = length - 1, s = length
744             ; i >= 0 && (s - i) <= numDigits ; i--
745         ) {
746             char c = np.charAt(i);
747 
748             ret.append(c);
749         }
750 
751         return ret.toString();
752     }
753 
754     /**
755      * Basically: makes sure there's a + in front of a
756      * TOA_International number
757      *
758      * Returns null if s == null
759      */
760     public static String
stringFromStringAndTOA(String s, int TOA)761     stringFromStringAndTOA(String s, int TOA) {
762         if (s == null) return null;
763 
764         if (TOA == TOA_International && s.length() > 0 && s.charAt(0) != '+') {
765             return "+" + s;
766         }
767 
768         return s;
769     }
770 
771     /**
772      * Returns the TOA for the given dial string
773      * Basically, returns TOA_International if there's a + prefix
774      */
775 
776     public static int
toaFromString(String s)777     toaFromString(String s) {
778         if (s != null && s.length() > 0 && s.charAt(0) == '+') {
779             return TOA_International;
780         }
781 
782         return TOA_Unknown;
783     }
784 
785     /**
786      *  3GPP TS 24.008 10.5.4.7
787      *  Called Party BCD Number
788      *
789      *  See Also TS 51.011 10.5.1 "dialing number/ssc string"
790      *  and TS 11.11 "10.3.1 EF adn (Abbreviated dialing numbers)"
791      *
792      * @param bytes the data buffer
793      * @param offset should point to the TOA (aka. TON/NPI) octet after the length byte
794      * @param length is the number of bytes including TOA byte
795      *                and must be at least 2
796      *
797      * @return partial string on invalid decode
798      *
799      * FIXME(mkf) support alphanumeric address type
800      *  currently implemented in SMSMessage.getAddress()
801      */
802     public static String
calledPartyBCDToString(byte[] bytes, int offset, int length)803     calledPartyBCDToString (byte[] bytes, int offset, int length) {
804         boolean prependPlus = false;
805         StringBuilder ret = new StringBuilder(1 + length * 2);
806 
807         if (length < 2) {
808             return "";
809         }
810 
811         //Only TON field should be taken in consideration
812         if ((bytes[offset] & 0xf0) == (TOA_International & 0xf0)) {
813             prependPlus = true;
814         }
815 
816         internalCalledPartyBCDFragmentToString(
817                 ret, bytes, offset + 1, length - 1);
818 
819         if (prependPlus && ret.length() == 0) {
820             // If the only thing there is a prepended plus, return ""
821             return "";
822         }
823 
824         if (prependPlus) {
825             // This is an "international number" and should have
826             // a plus prepended to the dialing number. But there
827             // can also be GSM MMI codes as defined in TS 22.030 6.5.2
828             // so we need to handle those also.
829             //
830             // http://web.telia.com/~u47904776/gsmkode.htm
831             // has a nice list of some of these GSM codes.
832             //
833             // Examples are:
834             //   **21*+886988171479#
835             //   **21*8311234567#
836             //   *21#
837             //   #21#
838             //   *#21#
839             //   *31#+11234567890
840             //   #31#+18311234567
841             //   #31#8311234567
842             //   18311234567
843             //   +18311234567#
844             //   +18311234567
845             // Odd ball cases that some phones handled
846             // where there is no dialing number so they
847             // append the "+"
848             //   *21#+
849             //   **21#+
850             String retString = ret.toString();
851             Pattern p = Pattern.compile("(^[#*])(.*)([#*])(.*)(#)$");
852             Matcher m = p.matcher(retString);
853             if (m.matches()) {
854                 if ("".equals(m.group(2))) {
855                     // Started with two [#*] ends with #
856                     // So no dialing number and we'll just
857                     // append a +, this handles **21#+
858                     ret = new StringBuilder();
859                     ret.append(m.group(1));
860                     ret.append(m.group(3));
861                     ret.append(m.group(4));
862                     ret.append(m.group(5));
863                     ret.append("+");
864                 } else {
865                     // Starts with [#*] and ends with #
866                     // Assume group 4 is a dialing number
867                     // such as *21*+1234554#
868                     ret = new StringBuilder();
869                     ret.append(m.group(1));
870                     ret.append(m.group(2));
871                     ret.append(m.group(3));
872                     ret.append("+");
873                     ret.append(m.group(4));
874                     ret.append(m.group(5));
875                 }
876             } else {
877                 p = Pattern.compile("(^[#*])(.*)([#*])(.*)");
878                 m = p.matcher(retString);
879                 if (m.matches()) {
880                     // Starts with [#*] and only one other [#*]
881                     // Assume the data after last [#*] is dialing
882                     // number (i.e. group 4) such as *31#+11234567890.
883                     // This also includes the odd ball *21#+
884                     ret = new StringBuilder();
885                     ret.append(m.group(1));
886                     ret.append(m.group(2));
887                     ret.append(m.group(3));
888                     ret.append("+");
889                     ret.append(m.group(4));
890                 } else {
891                     // Does NOT start with [#*] just prepend '+'
892                     ret = new StringBuilder();
893                     ret.append('+');
894                     ret.append(retString);
895                 }
896             }
897         }
898 
899         return ret.toString();
900     }
901 
902     private static void
internalCalledPartyBCDFragmentToString( StringBuilder sb, byte [] bytes, int offset, int length)903     internalCalledPartyBCDFragmentToString(
904         StringBuilder sb, byte [] bytes, int offset, int length) {
905         for (int i = offset ; i < length + offset ; i++) {
906             byte b;
907             char c;
908 
909             c = bcdToChar((byte)(bytes[i] & 0xf));
910 
911             if (c == 0) {
912                 return;
913             }
914             sb.append(c);
915 
916             // FIXME(mkf) TS 23.040 9.1.2.3 says
917             // "if a mobile receives 1111 in a position prior to
918             // the last semi-octet then processing shall commence with
919             // the next semi-octet and the intervening
920             // semi-octet shall be ignored"
921             // How does this jive with 24.008 10.5.4.7
922 
923             b = (byte)((bytes[i] >> 4) & 0xf);
924 
925             if (b == 0xf && i + 1 == length + offset) {
926                 //ignore final 0xf
927                 break;
928             }
929 
930             c = bcdToChar(b);
931             if (c == 0) {
932                 return;
933             }
934 
935             sb.append(c);
936         }
937 
938     }
939 
940     /**
941      * Like calledPartyBCDToString, but field does not start with a
942      * TOA byte. For example: SIM ADN extension fields
943      */
944 
945     public static String
calledPartyBCDFragmentToString(byte [] bytes, int offset, int length)946     calledPartyBCDFragmentToString(byte [] bytes, int offset, int length) {
947         StringBuilder ret = new StringBuilder(length * 2);
948 
949         internalCalledPartyBCDFragmentToString(ret, bytes, offset, length);
950 
951         return ret.toString();
952     }
953 
954     /** returns 0 on invalid value */
955     private static char
bcdToChar(byte b)956     bcdToChar(byte b) {
957         if (b < 0xa) {
958             return (char)('0' + b);
959         } else switch (b) {
960             case 0xa: return '*';
961             case 0xb: return '#';
962             case 0xc: return PAUSE;
963             case 0xd: return WILD;
964 
965             default: return 0;
966         }
967     }
968 
969     private static int
charToBCD(char c)970     charToBCD(char c) {
971         if (c >= '0' && c <= '9') {
972             return c - '0';
973         } else if (c == '*') {
974             return 0xa;
975         } else if (c == '#') {
976             return 0xb;
977         } else if (c == PAUSE) {
978             return 0xc;
979         } else if (c == WILD) {
980             return 0xd;
981         } else if (c == WAIT) {
982             return 0xe;
983         } else {
984             throw new RuntimeException ("invalid char for BCD " + c);
985         }
986     }
987 
988     /**
989      * Return true iff the network portion of <code>address</code> is,
990      * as far as we can tell on the device, suitable for use as an SMS
991      * destination address.
992      */
isWellFormedSmsAddress(String address)993     public static boolean isWellFormedSmsAddress(String address) {
994         String networkPortion =
995                 PhoneNumberUtils.extractNetworkPortion(address);
996 
997         return (!(networkPortion.equals("+")
998                   || TextUtils.isEmpty(networkPortion)))
999                && isDialable(networkPortion);
1000     }
1001 
isGlobalPhoneNumber(String phoneNumber)1002     public static boolean isGlobalPhoneNumber(String phoneNumber) {
1003         if (TextUtils.isEmpty(phoneNumber)) {
1004             return false;
1005         }
1006 
1007         Matcher match = GLOBAL_PHONE_NUMBER_PATTERN.matcher(phoneNumber);
1008         return match.matches();
1009     }
1010 
isDialable(String address)1011     private static boolean isDialable(String address) {
1012         for (int i = 0, count = address.length(); i < count; i++) {
1013             if (!isDialable(address.charAt(i))) {
1014                 return false;
1015             }
1016         }
1017         return true;
1018     }
1019 
isNonSeparator(String address)1020     private static boolean isNonSeparator(String address) {
1021         for (int i = 0, count = address.length(); i < count; i++) {
1022             if (!isNonSeparator(address.charAt(i))) {
1023                 return false;
1024             }
1025         }
1026         return true;
1027     }
1028     /**
1029      * Note: calls extractNetworkPortion(), so do not use for
1030      * SIM EF[ADN] style records
1031      *
1032      * Returns null if network portion is empty.
1033      */
1034     public static byte[]
networkPortionToCalledPartyBCD(String s)1035     networkPortionToCalledPartyBCD(String s) {
1036         String networkPortion = extractNetworkPortion(s);
1037         return numberToCalledPartyBCDHelper(networkPortion, false);
1038     }
1039 
1040     /**
1041      * Same as {@link #networkPortionToCalledPartyBCD}, but includes a
1042      * one-byte length prefix.
1043      */
1044     public static byte[]
networkPortionToCalledPartyBCDWithLength(String s)1045     networkPortionToCalledPartyBCDWithLength(String s) {
1046         String networkPortion = extractNetworkPortion(s);
1047         return numberToCalledPartyBCDHelper(networkPortion, true);
1048     }
1049 
1050     /**
1051      * Convert a dialing number to BCD byte array
1052      *
1053      * @param number dialing number string
1054      *        if the dialing number starts with '+', set to international TOA
1055      * @return BCD byte array
1056      */
1057     public static byte[]
numberToCalledPartyBCD(String number)1058     numberToCalledPartyBCD(String number) {
1059         return numberToCalledPartyBCDHelper(number, false);
1060     }
1061 
1062     /**
1063      * If includeLength is true, prepend a one-byte length value to
1064      * the return array.
1065      */
1066     private static byte[]
numberToCalledPartyBCDHelper(String number, boolean includeLength)1067     numberToCalledPartyBCDHelper(String number, boolean includeLength) {
1068         int numberLenReal = number.length();
1069         int numberLenEffective = numberLenReal;
1070         boolean hasPlus = number.indexOf('+') != -1;
1071         if (hasPlus) numberLenEffective--;
1072 
1073         if (numberLenEffective == 0) return null;
1074 
1075         int resultLen = (numberLenEffective + 1) / 2;  // Encoded numbers require only 4 bits each.
1076         int extraBytes = 1;                            // Prepended TOA byte.
1077         if (includeLength) extraBytes++;               // Optional prepended length byte.
1078         resultLen += extraBytes;
1079 
1080         byte[] result = new byte[resultLen];
1081 
1082         int digitCount = 0;
1083         for (int i = 0; i < numberLenReal; i++) {
1084             char c = number.charAt(i);
1085             if (c == '+') continue;
1086             int shift = ((digitCount & 0x01) == 1) ? 4 : 0;
1087             result[extraBytes + (digitCount >> 1)] |= (byte)((charToBCD(c) & 0x0F) << shift);
1088             digitCount++;
1089         }
1090 
1091         // 1-fill any trailing odd nibble/quartet.
1092         if ((digitCount & 0x01) == 1) result[extraBytes + (digitCount >> 1)] |= 0xF0;
1093 
1094         int offset = 0;
1095         if (includeLength) result[offset++] = (byte)(resultLen - 1);
1096         result[offset] = (byte)(hasPlus ? TOA_International : TOA_Unknown);
1097 
1098         return result;
1099     }
1100 
1101     //================ Number formatting =========================
1102 
1103     /** The current locale is unknown, look for a country code or don't format */
1104     public static final int FORMAT_UNKNOWN = 0;
1105     /** NANP formatting */
1106     public static final int FORMAT_NANP = 1;
1107     /** Japanese formatting */
1108     public static final int FORMAT_JAPAN = 2;
1109 
1110     /** List of country codes for countries that use the NANP */
1111     private static final String[] NANP_COUNTRIES = new String[] {
1112         "US", // United States
1113         "CA", // Canada
1114         "AS", // American Samoa
1115         "AI", // Anguilla
1116         "AG", // Antigua and Barbuda
1117         "BS", // Bahamas
1118         "BB", // Barbados
1119         "BM", // Bermuda
1120         "VG", // British Virgin Islands
1121         "KY", // Cayman Islands
1122         "DM", // Dominica
1123         "DO", // Dominican Republic
1124         "GD", // Grenada
1125         "GU", // Guam
1126         "JM", // Jamaica
1127         "PR", // Puerto Rico
1128         "MS", // Montserrat
1129         "MP", // Northern Mariana Islands
1130         "KN", // Saint Kitts and Nevis
1131         "LC", // Saint Lucia
1132         "VC", // Saint Vincent and the Grenadines
1133         "TT", // Trinidad and Tobago
1134         "TC", // Turks and Caicos Islands
1135         "VI", // U.S. Virgin Islands
1136     };
1137 
1138     private static final String KOREA_ISO_COUNTRY_CODE = "KR";
1139 
1140     /**
1141      * Breaks the given number down and formats it according to the rules
1142      * for the country the number is from.
1143      *
1144      * @param source The phone number to format
1145      * @return A locally acceptable formatting of the input, or the raw input if
1146      *  formatting rules aren't known for the number
1147      *
1148      * @deprecated Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead
1149      */
1150     @Deprecated
formatNumber(String source)1151     public static String formatNumber(String source) {
1152         SpannableStringBuilder text = new SpannableStringBuilder(source);
1153         formatNumber(text, getFormatTypeForLocale(Locale.getDefault()));
1154         return text.toString();
1155     }
1156 
1157     /**
1158      * Formats the given number with the given formatting type. Currently
1159      * {@link #FORMAT_NANP} and {@link #FORMAT_JAPAN} are supported as a formating type.
1160      *
1161      * @param source the phone number to format
1162      * @param defaultFormattingType The default formatting rules to apply if the number does
1163      * not begin with +[country_code]
1164      * @return The phone number formatted with the given formatting type.
1165      *
1166      * @hide
1167      * @deprecated Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead
1168      */
1169     @Deprecated
formatNumber(String source, int defaultFormattingType)1170     public static String formatNumber(String source, int defaultFormattingType) {
1171         SpannableStringBuilder text = new SpannableStringBuilder(source);
1172         formatNumber(text, defaultFormattingType);
1173         return text.toString();
1174     }
1175 
1176     /**
1177      * Returns the phone number formatting type for the given locale.
1178      *
1179      * @param locale The locale of interest, usually {@link Locale#getDefault()}
1180      * @return The formatting type for the given locale, or FORMAT_UNKNOWN if the formatting
1181      * rules are not known for the given locale
1182      *
1183      * @deprecated Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead
1184      */
1185     @Deprecated
getFormatTypeForLocale(Locale locale)1186     public static int getFormatTypeForLocale(Locale locale) {
1187         String country = locale.getCountry();
1188 
1189         return getFormatTypeFromCountryCode(country);
1190     }
1191 
1192     /**
1193      * Formats a phone number in-place. Currently {@link #FORMAT_JAPAN} and {@link #FORMAT_NANP}
1194      * is supported as a second argument.
1195      *
1196      * @param text The number to be formatted, will be modified with the formatting
1197      * @param defaultFormattingType The default formatting rules to apply if the number does
1198      * not begin with +[country_code]
1199      *
1200      * @deprecated Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead
1201      */
1202     @Deprecated
formatNumber(Editable text, int defaultFormattingType)1203     public static void formatNumber(Editable text, int defaultFormattingType) {
1204         int formatType = defaultFormattingType;
1205 
1206         if (text.length() > 2 && text.charAt(0) == '+') {
1207             if (text.charAt(1) == '1') {
1208                 formatType = FORMAT_NANP;
1209             } else if (text.length() >= 3 && text.charAt(1) == '8'
1210                 && text.charAt(2) == '1') {
1211                 formatType = FORMAT_JAPAN;
1212             } else {
1213                 formatType = FORMAT_UNKNOWN;
1214             }
1215         }
1216 
1217         switch (formatType) {
1218             case FORMAT_NANP:
1219                 formatNanpNumber(text);
1220                 return;
1221             case FORMAT_JAPAN:
1222                 formatJapaneseNumber(text);
1223                 return;
1224             case FORMAT_UNKNOWN:
1225                 removeDashes(text);
1226                 return;
1227         }
1228     }
1229 
1230     private static final int NANP_STATE_DIGIT = 1;
1231     private static final int NANP_STATE_PLUS = 2;
1232     private static final int NANP_STATE_ONE = 3;
1233     private static final int NANP_STATE_DASH = 4;
1234 
1235     /**
1236      * Formats a phone number in-place using the NANP formatting rules. Numbers will be formatted
1237      * as:
1238      *
1239      * <p><code>
1240      * xxxxx
1241      * xxx-xxxx
1242      * xxx-xxx-xxxx
1243      * 1-xxx-xxx-xxxx
1244      * +1-xxx-xxx-xxxx
1245      * </code></p>
1246      *
1247      * @param text the number to be formatted, will be modified with the formatting
1248      *
1249      * @deprecated Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead
1250      */
1251     @Deprecated
formatNanpNumber(Editable text)1252     public static void formatNanpNumber(Editable text) {
1253         int length = text.length();
1254         if (length > "+1-nnn-nnn-nnnn".length()) {
1255             // The string is too long to be formatted
1256             return;
1257         } else if (length <= 5) {
1258             // The string is either a shortcode or too short to be formatted
1259             return;
1260         }
1261 
1262         CharSequence saved = text.subSequence(0, length);
1263 
1264         // Strip the dashes first, as we're going to add them back
1265         removeDashes(text);
1266         length = text.length();
1267 
1268         // When scanning the number we record where dashes need to be added,
1269         // if they're non-0 at the end of the scan the dashes will be added in
1270         // the proper places.
1271         int dashPositions[] = new int[3];
1272         int numDashes = 0;
1273 
1274         int state = NANP_STATE_DIGIT;
1275         int numDigits = 0;
1276         for (int i = 0; i < length; i++) {
1277             char c = text.charAt(i);
1278             switch (c) {
1279                 case '1':
1280                     if (numDigits == 0 || state == NANP_STATE_PLUS) {
1281                         state = NANP_STATE_ONE;
1282                         break;
1283                     }
1284                     // fall through
1285                 case '2':
1286                 case '3':
1287                 case '4':
1288                 case '5':
1289                 case '6':
1290                 case '7':
1291                 case '8':
1292                 case '9':
1293                 case '0':
1294                     if (state == NANP_STATE_PLUS) {
1295                         // Only NANP number supported for now
1296                         text.replace(0, length, saved);
1297                         return;
1298                     } else if (state == NANP_STATE_ONE) {
1299                         // Found either +1 or 1, follow it up with a dash
1300                         dashPositions[numDashes++] = i;
1301                     } else if (state != NANP_STATE_DASH && (numDigits == 3 || numDigits == 6)) {
1302                         // Found a digit that should be after a dash that isn't
1303                         dashPositions[numDashes++] = i;
1304                     }
1305                     state = NANP_STATE_DIGIT;
1306                     numDigits++;
1307                     break;
1308 
1309                 case '-':
1310                     state = NANP_STATE_DASH;
1311                     break;
1312 
1313                 case '+':
1314                     if (i == 0) {
1315                         // Plus is only allowed as the first character
1316                         state = NANP_STATE_PLUS;
1317                         break;
1318                     }
1319                     // Fall through
1320                 default:
1321                     // Unknown character, bail on formatting
1322                     text.replace(0, length, saved);
1323                     return;
1324             }
1325         }
1326 
1327         if (numDigits == 7) {
1328             // With 7 digits we want xxx-xxxx, not xxx-xxx-x
1329             numDashes--;
1330         }
1331 
1332         // Actually put the dashes in place
1333         for (int i = 0; i < numDashes; i++) {
1334             int pos = dashPositions[i];
1335             text.replace(pos + i, pos + i, "-");
1336         }
1337 
1338         // Remove trailing dashes
1339         int len = text.length();
1340         while (len > 0) {
1341             if (text.charAt(len - 1) == '-') {
1342                 text.delete(len - 1, len);
1343                 len--;
1344             } else {
1345                 break;
1346             }
1347         }
1348     }
1349 
1350     /**
1351      * Formats a phone number in-place using the Japanese formatting rules.
1352      * Numbers will be formatted as:
1353      *
1354      * <p><code>
1355      * 03-xxxx-xxxx
1356      * 090-xxxx-xxxx
1357      * 0120-xxx-xxx
1358      * +81-3-xxxx-xxxx
1359      * +81-90-xxxx-xxxx
1360      * </code></p>
1361      *
1362      * @param text the number to be formatted, will be modified with
1363      * the formatting
1364      *
1365      * @deprecated Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead
1366      */
1367     @Deprecated
formatJapaneseNumber(Editable text)1368     public static void formatJapaneseNumber(Editable text) {
1369         JapanesePhoneNumberFormatter.format(text);
1370     }
1371 
1372     /**
1373      * Removes all dashes from the number.
1374      *
1375      * @param text the number to clear from dashes
1376      */
removeDashes(Editable text)1377     private static void removeDashes(Editable text) {
1378         int p = 0;
1379         while (p < text.length()) {
1380             if (text.charAt(p) == '-') {
1381                 text.delete(p, p + 1);
1382            } else {
1383                 p++;
1384            }
1385         }
1386     }
1387 
1388     /**
1389      * Formats the specified {@code phoneNumber} to the E.164 representation.
1390      *
1391      * @param phoneNumber the phone number to format.
1392      * @param defaultCountryIso the ISO 3166-1 two letters country code.
1393      * @return the E.164 representation, or null if the given phone number is not valid.
1394      */
formatNumberToE164(String phoneNumber, String defaultCountryIso)1395     public static String formatNumberToE164(String phoneNumber, String defaultCountryIso) {
1396         return formatNumberInternal(phoneNumber, defaultCountryIso, PhoneNumberFormat.E164);
1397     }
1398 
1399     /**
1400      * Formats the specified {@code phoneNumber} to the RFC3966 representation.
1401      *
1402      * @param phoneNumber the phone number to format.
1403      * @param defaultCountryIso the ISO 3166-1 two letters country code.
1404      * @return the RFC3966 representation, or null if the given phone number is not valid.
1405      */
formatNumberToRFC3966(String phoneNumber, String defaultCountryIso)1406     public static String formatNumberToRFC3966(String phoneNumber, String defaultCountryIso) {
1407         return formatNumberInternal(phoneNumber, defaultCountryIso, PhoneNumberFormat.RFC3966);
1408     }
1409 
1410     /**
1411      * Formats the raw phone number (string) using the specified {@code formatIdentifier}.
1412      * <p>
1413      * The given phone number must have an area code and could have a country code.
1414      * <p>
1415      * The defaultCountryIso is used to validate the given number and generate the formatted number
1416      * if the specified number doesn't have a country code.
1417      *
1418      * @param rawPhoneNumber The phone number to format.
1419      * @param defaultCountryIso The ISO 3166-1 two letters country code.
1420      * @param formatIdentifier The (enum) identifier of the desired format.
1421      * @return the formatted representation, or null if the specified number is not valid.
1422      */
formatNumberInternal( String rawPhoneNumber, String defaultCountryIso, PhoneNumberFormat formatIdentifier)1423     private static String formatNumberInternal(
1424             String rawPhoneNumber, String defaultCountryIso, PhoneNumberFormat formatIdentifier) {
1425 
1426         PhoneNumberUtil util = PhoneNumberUtil.getInstance();
1427         try {
1428             PhoneNumber phoneNumber = util.parse(rawPhoneNumber, defaultCountryIso);
1429             if (util.isValidNumber(phoneNumber)) {
1430                 return util.format(phoneNumber, formatIdentifier);
1431             }
1432         } catch (NumberParseException ignored) { }
1433 
1434         return null;
1435     }
1436 
1437     /**
1438      * Format a phone number.
1439      * <p>
1440      * If the given number doesn't have the country code, the phone will be
1441      * formatted to the default country's convention.
1442      *
1443      * @param phoneNumber
1444      *            the number to be formatted.
1445      * @param defaultCountryIso
1446      *            the ISO 3166-1 two letters country code whose convention will
1447      *            be used if the given number doesn't have the country code.
1448      * @return the formatted number, or null if the given number is not valid.
1449      */
formatNumber(String phoneNumber, String defaultCountryIso)1450     public static String formatNumber(String phoneNumber, String defaultCountryIso) {
1451         // Do not attempt to format numbers that start with a hash or star symbol.
1452         if (phoneNumber.startsWith("#") || phoneNumber.startsWith("*")) {
1453             return phoneNumber;
1454         }
1455 
1456         PhoneNumberUtil util = PhoneNumberUtil.getInstance();
1457         String result = null;
1458         try {
1459             PhoneNumber pn = util.parseAndKeepRawInput(phoneNumber, defaultCountryIso);
1460             /**
1461              * Need to reformat any local Korean phone numbers (when the user is in Korea) with
1462              * country code to corresponding national format which would replace the leading
1463              * +82 with 0.
1464              */
1465             if (KOREA_ISO_COUNTRY_CODE.equals(defaultCountryIso) &&
1466                     (pn.getCountryCode() == util.getCountryCodeForRegion(KOREA_ISO_COUNTRY_CODE)) &&
1467                     (pn.getCountryCodeSource() ==
1468                             PhoneNumber.CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN)) {
1469                 result = util.format(pn, PhoneNumberUtil.PhoneNumberFormat.NATIONAL);
1470             } else {
1471                 result = util.formatInOriginalFormat(pn, defaultCountryIso);
1472             }
1473         } catch (NumberParseException e) {
1474         }
1475         return result;
1476     }
1477 
1478     /**
1479      * Format the phone number only if the given number hasn't been formatted.
1480      * <p>
1481      * The number which has only dailable character is treated as not being
1482      * formatted.
1483      *
1484      * @param phoneNumber
1485      *            the number to be formatted.
1486      * @param phoneNumberE164
1487      *            the E164 format number whose country code is used if the given
1488      *            phoneNumber doesn't have the country code.
1489      * @param defaultCountryIso
1490      *            the ISO 3166-1 two letters country code whose convention will
1491      *            be used if the phoneNumberE164 is null or invalid, or if phoneNumber
1492      *            contains IDD.
1493      * @return the formatted number if the given number has been formatted,
1494      *            otherwise, return the given number.
1495      */
formatNumber( String phoneNumber, String phoneNumberE164, String defaultCountryIso)1496     public static String formatNumber(
1497             String phoneNumber, String phoneNumberE164, String defaultCountryIso) {
1498         int len = phoneNumber.length();
1499         for (int i = 0; i < len; i++) {
1500             if (!isDialable(phoneNumber.charAt(i))) {
1501                 return phoneNumber;
1502             }
1503         }
1504         PhoneNumberUtil util = PhoneNumberUtil.getInstance();
1505         // Get the country code from phoneNumberE164
1506         if (phoneNumberE164 != null && phoneNumberE164.length() >= 2
1507                 && phoneNumberE164.charAt(0) == '+') {
1508             try {
1509                 // The number to be parsed is in E164 format, so the default region used doesn't
1510                 // matter.
1511                 PhoneNumber pn = util.parse(phoneNumberE164, "ZZ");
1512                 String regionCode = util.getRegionCodeForNumber(pn);
1513                 if (!TextUtils.isEmpty(regionCode) &&
1514                     // This makes sure phoneNumber doesn't contain an IDD
1515                     normalizeNumber(phoneNumber).indexOf(phoneNumberE164.substring(1)) <= 0) {
1516                     defaultCountryIso = regionCode;
1517                 }
1518             } catch (NumberParseException e) {
1519             }
1520         }
1521         String result = formatNumber(phoneNumber, defaultCountryIso);
1522         return result != null ? result : phoneNumber;
1523     }
1524 
1525     /**
1526      * Normalize a phone number by removing the characters other than digits. If
1527      * the given number has keypad letters, the letters will be converted to
1528      * digits first.
1529      *
1530      * @param phoneNumber the number to be normalized.
1531      * @return the normalized number.
1532      */
normalizeNumber(String phoneNumber)1533     public static String normalizeNumber(String phoneNumber) {
1534         if (TextUtils.isEmpty(phoneNumber)) {
1535             return "";
1536         }
1537 
1538         StringBuilder sb = new StringBuilder();
1539         int len = phoneNumber.length();
1540         for (int i = 0; i < len; i++) {
1541             char c = phoneNumber.charAt(i);
1542             // Character.digit() supports ASCII and Unicode digits (fullwidth, Arabic-Indic, etc.)
1543             int digit = Character.digit(c, 10);
1544             if (digit != -1) {
1545                 sb.append(digit);
1546             } else if (sb.length() == 0 && c == '+') {
1547                 sb.append(c);
1548             } else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
1549                 return normalizeNumber(PhoneNumberUtils.convertKeypadLettersToDigits(phoneNumber));
1550             }
1551         }
1552         return sb.toString();
1553     }
1554 
1555     /**
1556      * Replaces all unicode(e.g. Arabic, Persian) digits with their decimal digit equivalents.
1557      *
1558      * @param number the number to perform the replacement on.
1559      * @return the replaced number.
1560      */
replaceUnicodeDigits(String number)1561     public static String replaceUnicodeDigits(String number) {
1562         StringBuilder normalizedDigits = new StringBuilder(number.length());
1563         for (char c : number.toCharArray()) {
1564             int digit = Character.digit(c, 10);
1565             if (digit != -1) {
1566                 normalizedDigits.append(digit);
1567             } else {
1568                 normalizedDigits.append(c);
1569             }
1570         }
1571         return normalizedDigits.toString();
1572     }
1573 
1574     // Three and four digit phone numbers for either special services,
1575     // or 3-6 digit addresses from the network (eg carrier-originated SMS messages) should
1576     // not match.
1577     //
1578     // This constant used to be 5, but SMS short codes has increased in length and
1579     // can be easily 6 digits now days. Most countries have SMS short code length between
1580     // 3 to 6 digits. The exceptions are
1581     //
1582     // Australia: Short codes are six or eight digits in length, starting with the prefix "19"
1583     //            followed by an additional four or six digits and two.
1584     // Czech Republic: Codes are seven digits in length for MO and five (not billed) or
1585     //            eight (billed) for MT direction
1586     //
1587     // see http://en.wikipedia.org/wiki/Short_code#Regional_differences for reference
1588     //
1589     // However, in order to loose match 650-555-1212 and 555-1212, we need to set the min match
1590     // to 7.
1591     static final int MIN_MATCH = 7;
1592 
1593     /**
1594      * Checks a given number against the list of
1595      * emergency numbers provided by the RIL and SIM card.
1596      *
1597      * @param number the number to look up.
1598      * @return true if the number is in the list of emergency numbers
1599      *         listed in the RIL / SIM, otherwise return false.
1600      */
isEmergencyNumber(String number)1601     public static boolean isEmergencyNumber(String number) {
1602         return isEmergencyNumber(getDefaultVoiceSubId(), number);
1603     }
1604 
1605     /**
1606      * Checks a given number against the list of
1607      * emergency numbers provided by the RIL and SIM card.
1608      *
1609      * @param subId the subscription id of the SIM.
1610      * @param number the number to look up.
1611      * @return true if the number is in the list of emergency numbers
1612      *         listed in the RIL / SIM, otherwise return false.
1613      * @hide
1614      */
isEmergencyNumber(int subId, String number)1615     public static boolean isEmergencyNumber(int subId, String number) {
1616         // Return true only if the specified number *exactly* matches
1617         // one of the emergency numbers listed by the RIL / SIM.
1618         return isEmergencyNumberInternal(subId, number, true /* useExactMatch */);
1619     }
1620 
1621     /**
1622      * Checks if given number might *potentially* result in
1623      * a call to an emergency service on the current network.
1624      *
1625      * Specifically, this method will return true if the specified number
1626      * is an emergency number according to the list managed by the RIL or
1627      * SIM, *or* if the specified number simply starts with the same
1628      * digits as any of the emergency numbers listed in the RIL / SIM.
1629      *
1630      * This method is intended for internal use by the phone app when
1631      * deciding whether to allow ACTION_CALL intents from 3rd party apps
1632      * (where we're required to *not* allow emergency calls to be placed.)
1633      *
1634      * @param number the number to look up.
1635      * @return true if the number is in the list of emergency numbers
1636      *         listed in the RIL / SIM, *or* if the number starts with the
1637      *         same digits as any of those emergency numbers.
1638      *
1639      * @hide
1640      */
isPotentialEmergencyNumber(String number)1641     public static boolean isPotentialEmergencyNumber(String number) {
1642         return isPotentialEmergencyNumber(getDefaultVoiceSubId(), number);
1643     }
1644 
1645     /**
1646      * Checks if given number might *potentially* result in
1647      * a call to an emergency service on the current network.
1648      *
1649      * Specifically, this method will return true if the specified number
1650      * is an emergency number according to the list managed by the RIL or
1651      * SIM, *or* if the specified number simply starts with the same
1652      * digits as any of the emergency numbers listed in the RIL / SIM.
1653      *
1654      * This method is intended for internal use by the phone app when
1655      * deciding whether to allow ACTION_CALL intents from 3rd party apps
1656      * (where we're required to *not* allow emergency calls to be placed.)
1657      *
1658      * @param subId the subscription id of the SIM.
1659      * @param number the number to look up.
1660      * @return true if the number is in the list of emergency numbers
1661      *         listed in the RIL / SIM, *or* if the number starts with the
1662      *         same digits as any of those emergency numbers.
1663      * @hide
1664      */
isPotentialEmergencyNumber(int subId, String number)1665     public static boolean isPotentialEmergencyNumber(int subId, String number) {
1666         // Check against the emergency numbers listed by the RIL / SIM,
1667         // and *don't* require an exact match.
1668         return isEmergencyNumberInternal(subId, number, false /* useExactMatch */);
1669     }
1670 
1671     /**
1672      * Helper function for isEmergencyNumber(String) and
1673      * isPotentialEmergencyNumber(String).
1674      *
1675      * @param number the number to look up.
1676      *
1677      * @param useExactMatch if true, consider a number to be an emergency
1678      *           number only if it *exactly* matches a number listed in
1679      *           the RIL / SIM.  If false, a number is considered to be an
1680      *           emergency number if it simply starts with the same digits
1681      *           as any of the emergency numbers listed in the RIL / SIM.
1682      *           (Setting useExactMatch to false allows you to identify
1683      *           number that could *potentially* result in emergency calls
1684      *           since many networks will actually ignore trailing digits
1685      *           after a valid emergency number.)
1686      *
1687      * @return true if the number is in the list of emergency numbers
1688      *         listed in the RIL / sim, otherwise return false.
1689      */
isEmergencyNumberInternal(String number, boolean useExactMatch)1690     private static boolean isEmergencyNumberInternal(String number, boolean useExactMatch) {
1691         return isEmergencyNumberInternal(getDefaultVoiceSubId(), number, useExactMatch);
1692     }
1693 
1694     /**
1695      * Helper function for isEmergencyNumber(String) and
1696      * isPotentialEmergencyNumber(String).
1697      *
1698      * @param subId the subscription id of the SIM.
1699      * @param number the number to look up.
1700      *
1701      * @param useExactMatch if true, consider a number to be an emergency
1702      *           number only if it *exactly* matches a number listed in
1703      *           the RIL / SIM.  If false, a number is considered to be an
1704      *           emergency number if it simply starts with the same digits
1705      *           as any of the emergency numbers listed in the RIL / SIM.
1706      *           (Setting useExactMatch to false allows you to identify
1707      *           number that could *potentially* result in emergency calls
1708      *           since many networks will actually ignore trailing digits
1709      *           after a valid emergency number.)
1710      *
1711      * @return true if the number is in the list of emergency numbers
1712      *         listed in the RIL / sim, otherwise return false.
1713      */
isEmergencyNumberInternal(int subId, String number, boolean useExactMatch)1714     private static boolean isEmergencyNumberInternal(int subId, String number,
1715             boolean useExactMatch) {
1716         return isEmergencyNumberInternal(subId, number, null, useExactMatch);
1717     }
1718 
1719     /**
1720      * Checks if a given number is an emergency number for a specific country.
1721      *
1722      * @param number the number to look up.
1723      * @param defaultCountryIso the specific country which the number should be checked against
1724      * @return if the number is an emergency number for the specific country, then return true,
1725      * otherwise false
1726      *
1727      * @hide
1728      */
isEmergencyNumber(String number, String defaultCountryIso)1729     public static boolean isEmergencyNumber(String number, String defaultCountryIso) {
1730             return isEmergencyNumber(getDefaultVoiceSubId(), number, defaultCountryIso);
1731     }
1732 
1733     /**
1734      * Checks if a given number is an emergency number for a specific country.
1735      *
1736      * @param subId the subscription id of the SIM.
1737      * @param number the number to look up.
1738      * @param defaultCountryIso the specific country which the number should be checked against
1739      * @return if the number is an emergency number for the specific country, then return true,
1740      * otherwise false
1741      * @hide
1742      */
isEmergencyNumber(int subId, String number, String defaultCountryIso)1743     public static boolean isEmergencyNumber(int subId, String number, String defaultCountryIso) {
1744         return isEmergencyNumberInternal(subId, number,
1745                                          defaultCountryIso,
1746                                          true /* useExactMatch */);
1747     }
1748 
1749     /**
1750      * Checks if a given number might *potentially* result in a call to an
1751      * emergency service, for a specific country.
1752      *
1753      * Specifically, this method will return true if the specified number
1754      * is an emergency number in the specified country, *or* if the number
1755      * simply starts with the same digits as any emergency number for that
1756      * country.
1757      *
1758      * This method is intended for internal use by the phone app when
1759      * deciding whether to allow ACTION_CALL intents from 3rd party apps
1760      * (where we're required to *not* allow emergency calls to be placed.)
1761      *
1762      * @param number the number to look up.
1763      * @param defaultCountryIso the specific country which the number should be checked against
1764      * @return true if the number is an emergency number for the specific
1765      *         country, *or* if the number starts with the same digits as
1766      *         any of those emergency numbers.
1767      *
1768      * @hide
1769      */
isPotentialEmergencyNumber(String number, String defaultCountryIso)1770     public static boolean isPotentialEmergencyNumber(String number, String defaultCountryIso) {
1771         return isPotentialEmergencyNumber(getDefaultVoiceSubId(), number, defaultCountryIso);
1772     }
1773 
1774     /**
1775      * Checks if a given number might *potentially* result in a call to an
1776      * emergency service, for a specific country.
1777      *
1778      * Specifically, this method will return true if the specified number
1779      * is an emergency number in the specified country, *or* if the number
1780      * simply starts with the same digits as any emergency number for that
1781      * country.
1782      *
1783      * This method is intended for internal use by the phone app when
1784      * deciding whether to allow ACTION_CALL intents from 3rd party apps
1785      * (where we're required to *not* allow emergency calls to be placed.)
1786      *
1787      * @param subId the subscription id of the SIM.
1788      * @param number the number to look up.
1789      * @param defaultCountryIso the specific country which the number should be checked against
1790      * @return true if the number is an emergency number for the specific
1791      *         country, *or* if the number starts with the same digits as
1792      *         any of those emergency numbers.
1793      * @hide
1794      */
isPotentialEmergencyNumber(int subId, String number, String defaultCountryIso)1795     public static boolean isPotentialEmergencyNumber(int subId, String number,
1796             String defaultCountryIso) {
1797         return isEmergencyNumberInternal(subId, number,
1798                                          defaultCountryIso,
1799                                          false /* useExactMatch */);
1800     }
1801 
1802     /**
1803      * Helper function for isEmergencyNumber(String, String) and
1804      * isPotentialEmergencyNumber(String, String).
1805      *
1806      * @param number the number to look up.
1807      * @param defaultCountryIso the specific country which the number should be checked against
1808      * @param useExactMatch if true, consider a number to be an emergency
1809      *           number only if it *exactly* matches a number listed in
1810      *           the RIL / SIM.  If false, a number is considered to be an
1811      *           emergency number if it simply starts with the same digits
1812      *           as any of the emergency numbers listed in the RIL / SIM.
1813      *
1814      * @return true if the number is an emergency number for the specified country.
1815      */
isEmergencyNumberInternal(String number, String defaultCountryIso, boolean useExactMatch)1816     private static boolean isEmergencyNumberInternal(String number,
1817                                                      String defaultCountryIso,
1818                                                      boolean useExactMatch) {
1819         return isEmergencyNumberInternal(getDefaultVoiceSubId(), number, defaultCountryIso,
1820                 useExactMatch);
1821     }
1822 
1823     /**
1824      * Helper function for isEmergencyNumber(String, String) and
1825      * isPotentialEmergencyNumber(String, String).
1826      *
1827      * @param subId the subscription id of the SIM.
1828      * @param number the number to look up.
1829      * @param defaultCountryIso the specific country which the number should be checked against
1830      * @param useExactMatch if true, consider a number to be an emergency
1831      *           number only if it *exactly* matches a number listed in
1832      *           the RIL / SIM.  If false, a number is considered to be an
1833      *           emergency number if it simply starts with the same digits
1834      *           as any of the emergency numbers listed in the RIL / SIM.
1835      *
1836      * @return true if the number is an emergency number for the specified country.
1837      * @hide
1838      */
isEmergencyNumberInternal(int subId, String number, String defaultCountryIso, boolean useExactMatch)1839     private static boolean isEmergencyNumberInternal(int subId, String number,
1840                                                      String defaultCountryIso,
1841                                                      boolean useExactMatch) {
1842         // If the number passed in is null, just return false:
1843         if (number == null) return false;
1844 
1845         // If the number passed in is a SIP address, return false, since the
1846         // concept of "emergency numbers" is only meaningful for calls placed
1847         // over the cell network.
1848         // (Be sure to do this check *before* calling extractNetworkPortionAlt(),
1849         // since the whole point of extractNetworkPortionAlt() is to filter out
1850         // any non-dialable characters (which would turn 'abc911def@example.com'
1851         // into '911', for example.))
1852         if (isUriNumber(number)) {
1853             return false;
1854         }
1855 
1856         // Strip the separators from the number before comparing it
1857         // to the list.
1858         number = extractNetworkPortionAlt(number);
1859 
1860         Rlog.d(LOG_TAG, "subId:" + subId + ", defaultCountryIso:" +
1861                 ((defaultCountryIso == null) ? "NULL" : defaultCountryIso));
1862 
1863         String emergencyNumbers = "";
1864         int slotId = SubscriptionManager.getSlotId(subId);
1865 
1866         // retrieve the list of emergency numbers
1867         // check read-write ecclist property first
1868         String ecclist = (slotId <= 0) ? "ril.ecclist" : ("ril.ecclist" + slotId);
1869 
1870         emergencyNumbers = SystemProperties.get(ecclist, "");
1871 
1872         Rlog.d(LOG_TAG, "slotId:" + slotId + ", emergencyNumbers: " +  emergencyNumbers);
1873 
1874         if (TextUtils.isEmpty(emergencyNumbers)) {
1875             // then read-only ecclist property since old RIL only uses this
1876             emergencyNumbers = SystemProperties.get("ro.ril.ecclist");
1877         }
1878 
1879         if (!TextUtils.isEmpty(emergencyNumbers)) {
1880             // searches through the comma-separated list for a match,
1881             // return true if one is found.
1882             for (String emergencyNum : emergencyNumbers.split(",")) {
1883                 // It is not possible to append additional digits to an emergency number to dial
1884                 // the number in Brazil - it won't connect.
1885                 if (useExactMatch || "BR".equalsIgnoreCase(defaultCountryIso)) {
1886                     if (number.equals(emergencyNum)) {
1887                         return true;
1888                     }
1889                 } else {
1890                     if (number.startsWith(emergencyNum)) {
1891                         return true;
1892                     }
1893                 }
1894             }
1895             // no matches found against the list!
1896             return false;
1897         }
1898 
1899         Rlog.d(LOG_TAG, "System property doesn't provide any emergency numbers."
1900                 + " Use embedded logic for determining ones.");
1901 
1902         // If slot id is invalid, means that there is no sim card.
1903         // According spec 3GPP TS22.101, the following numbers should be
1904         // ECC numbers when SIM/USIM is not present.
1905         emergencyNumbers = ((slotId < 0) ? "112,911,000,08,110,118,119,999" : "112,911");
1906 
1907         for (String emergencyNum : emergencyNumbers.split(",")) {
1908             if (useExactMatch) {
1909                 if (number.equals(emergencyNum)) {
1910                     return true;
1911                 }
1912             } else {
1913                 if (number.startsWith(emergencyNum)) {
1914                     return true;
1915                 }
1916             }
1917         }
1918 
1919         // No ecclist system property, so use our own list.
1920         if (defaultCountryIso != null) {
1921             ShortNumberUtil util = new ShortNumberUtil();
1922             if (useExactMatch) {
1923                 return util.isEmergencyNumber(number, defaultCountryIso);
1924             } else {
1925                 return util.connectsToEmergencyNumber(number, defaultCountryIso);
1926             }
1927         }
1928 
1929         return false;
1930     }
1931 
1932     /**
1933      * Checks if a given number is an emergency number for the country that the user is in.
1934      *
1935      * @param number the number to look up.
1936      * @param context the specific context which the number should be checked against
1937      * @return true if the specified number is an emergency number for the country the user
1938      * is currently in.
1939      */
isLocalEmergencyNumber(Context context, String number)1940     public static boolean isLocalEmergencyNumber(Context context, String number) {
1941         return isLocalEmergencyNumber(context, getDefaultVoiceSubId(), number);
1942     }
1943 
1944     /**
1945      * Checks if a given number is an emergency number for the country that the user is in.
1946      *
1947      * @param subId the subscription id of the SIM.
1948      * @param number the number to look up.
1949      * @param context the specific context which the number should be checked against
1950      * @return true if the specified number is an emergency number for the country the user
1951      * is currently in.
1952      * @hide
1953      */
isLocalEmergencyNumber(Context context, int subId, String number)1954     public static boolean isLocalEmergencyNumber(Context context, int subId, String number) {
1955         return isLocalEmergencyNumberInternal(subId, number,
1956                                               context,
1957                                               true /* useExactMatch */);
1958     }
1959 
1960     /**
1961      * Checks if a given number might *potentially* result in a call to an
1962      * emergency service, for the country that the user is in. The current
1963      * country is determined using the CountryDetector.
1964      *
1965      * Specifically, this method will return true if the specified number
1966      * is an emergency number in the current country, *or* if the number
1967      * simply starts with the same digits as any emergency number for the
1968      * current country.
1969      *
1970      * This method is intended for internal use by the phone app when
1971      * deciding whether to allow ACTION_CALL intents from 3rd party apps
1972      * (where we're required to *not* allow emergency calls to be placed.)
1973      *
1974      * @param number the number to look up.
1975      * @param context the specific context which the number should be checked against
1976      * @return true if the specified number is an emergency number for a local country, based on the
1977      *              CountryDetector.
1978      *
1979      * @see android.location.CountryDetector
1980      * @hide
1981      */
isPotentialLocalEmergencyNumber(Context context, String number)1982     public static boolean isPotentialLocalEmergencyNumber(Context context, String number) {
1983         return isPotentialLocalEmergencyNumber(context, getDefaultVoiceSubId(), number);
1984     }
1985 
1986     /**
1987      * Checks if a given number might *potentially* result in a call to an
1988      * emergency service, for the country that the user is in. The current
1989      * country is determined using the CountryDetector.
1990      *
1991      * Specifically, this method will return true if the specified number
1992      * is an emergency number in the current country, *or* if the number
1993      * simply starts with the same digits as any emergency number for the
1994      * current country.
1995      *
1996      * This method is intended for internal use by the phone app when
1997      * deciding whether to allow ACTION_CALL intents from 3rd party apps
1998      * (where we're required to *not* allow emergency calls to be placed.)
1999      *
2000      * @param subId the subscription id of the SIM.
2001      * @param number the number to look up.
2002      * @param context the specific context which the number should be checked against
2003      * @return true if the specified number is an emergency number for a local country, based on the
2004      *              CountryDetector.
2005      *
2006      * @hide
2007      */
isPotentialLocalEmergencyNumber(Context context, int subId, String number)2008     public static boolean isPotentialLocalEmergencyNumber(Context context, int subId,
2009             String number) {
2010         return isLocalEmergencyNumberInternal(subId, number,
2011                                               context,
2012                                               false /* useExactMatch */);
2013     }
2014 
2015     /**
2016      * Helper function for isLocalEmergencyNumber() and
2017      * isPotentialLocalEmergencyNumber().
2018      *
2019      * @param number the number to look up.
2020      * @param context the specific context which the number should be checked against
2021      * @param useExactMatch if true, consider a number to be an emergency
2022      *           number only if it *exactly* matches a number listed in
2023      *           the RIL / SIM.  If false, a number is considered to be an
2024      *           emergency number if it simply starts with the same digits
2025      *           as any of the emergency numbers listed in the RIL / SIM.
2026      *
2027      * @return true if the specified number is an emergency number for a
2028      *              local country, based on the CountryDetector.
2029      *
2030      * @see android.location.CountryDetector
2031      * @hide
2032      */
isLocalEmergencyNumberInternal(String number, Context context, boolean useExactMatch)2033     private static boolean isLocalEmergencyNumberInternal(String number,
2034                                                           Context context,
2035                                                           boolean useExactMatch) {
2036         return isLocalEmergencyNumberInternal(getDefaultVoiceSubId(), number, context,
2037                 useExactMatch);
2038     }
2039 
2040     /**
2041      * Helper function for isLocalEmergencyNumber() and
2042      * isPotentialLocalEmergencyNumber().
2043      *
2044      * @param subId the subscription id of the SIM.
2045      * @param number the number to look up.
2046      * @param context the specific context which the number should be checked against
2047      * @param useExactMatch if true, consider a number to be an emergency
2048      *           number only if it *exactly* matches a number listed in
2049      *           the RIL / SIM.  If false, a number is considered to be an
2050      *           emergency number if it simply starts with the same digits
2051      *           as any of the emergency numbers listed in the RIL / SIM.
2052      *
2053      * @return true if the specified number is an emergency number for a
2054      *              local country, based on the CountryDetector.
2055      * @hide
2056      */
isLocalEmergencyNumberInternal(int subId, String number, Context context, boolean useExactMatch)2057     private static boolean isLocalEmergencyNumberInternal(int subId, String number,
2058                                                           Context context,
2059                                                           boolean useExactMatch) {
2060         String countryIso;
2061         CountryDetector detector = (CountryDetector) context.getSystemService(
2062                 Context.COUNTRY_DETECTOR);
2063         if (detector != null && detector.detectCountry() != null) {
2064             countryIso = detector.detectCountry().getCountryIso();
2065         } else {
2066             Locale locale = context.getResources().getConfiguration().locale;
2067             countryIso = locale.getCountry();
2068             Rlog.w(LOG_TAG, "No CountryDetector; falling back to countryIso based on locale: "
2069                     + countryIso);
2070         }
2071         return isEmergencyNumberInternal(subId, number, countryIso, useExactMatch);
2072     }
2073 
2074     /**
2075      * isVoiceMailNumber: checks a given number against the voicemail
2076      *   number provided by the RIL and SIM card. The caller must have
2077      *   the READ_PHONE_STATE credential.
2078      *
2079      * @param number the number to look up.
2080      * @return true if the number is in the list of voicemail. False
2081      * otherwise, including if the caller does not have the permission
2082      * to read the VM number.
2083      */
isVoiceMailNumber(String number)2084     public static boolean isVoiceMailNumber(String number) {
2085         return isVoiceMailNumber(SubscriptionManager.getDefaultSubId(), number);
2086     }
2087 
2088     /**
2089      * isVoiceMailNumber: checks a given number against the voicemail
2090      *   number provided by the RIL and SIM card. The caller must have
2091      *   the READ_PHONE_STATE credential.
2092      *
2093      * @param subId the subscription id of the SIM.
2094      * @param number the number to look up.
2095      * @return true if the number is in the list of voicemail. False
2096      * otherwise, including if the caller does not have the permission
2097      * to read the VM number.
2098      * @hide
2099      */
isVoiceMailNumber(int subId, String number)2100     public static boolean isVoiceMailNumber(int subId, String number) {
2101         return isVoiceMailNumber(null, subId, number);
2102     }
2103 
2104     /**
2105      * isVoiceMailNumber: checks a given number against the voicemail
2106      *   number provided by the RIL and SIM card. The caller must have
2107      *   the READ_PHONE_STATE credential.
2108      *
2109      * @param context a non-null {@link Context}.
2110      * @param subId the subscription id of the SIM.
2111      * @param number the number to look up.
2112      * @return true if the number is in the list of voicemail. False
2113      * otherwise, including if the caller does not have the permission
2114      * to read the VM number.
2115      * @hide
2116      */
isVoiceMailNumber(Context context, int subId, String number)2117     public static boolean isVoiceMailNumber(Context context, int subId, String number) {
2118         String vmNumber;
2119         try {
2120             final TelephonyManager tm;
2121             if (context == null) {
2122                 tm = TelephonyManager.getDefault();
2123             } else {
2124                 tm = TelephonyManager.from(context);
2125             }
2126             vmNumber = tm.getVoiceMailNumber(subId);
2127         } catch (SecurityException ex) {
2128             return false;
2129         }
2130         // Strip the separators from the number before comparing it
2131         // to the list.
2132         number = extractNetworkPortionAlt(number);
2133 
2134         // compare tolerates null so we need to make sure that we
2135         // don't return true when both are null.
2136         return !TextUtils.isEmpty(number) && compare(number, vmNumber);
2137     }
2138 
2139     /**
2140      * Translates any alphabetic letters (i.e. [A-Za-z]) in the
2141      * specified phone number into the equivalent numeric digits,
2142      * according to the phone keypad letter mapping described in
2143      * ITU E.161 and ISO/IEC 9995-8.
2144      *
2145      * @return the input string, with alpha letters converted to numeric
2146      *         digits using the phone keypad letter mapping.  For example,
2147      *         an input of "1-800-GOOG-411" will return "1-800-4664-411".
2148      */
convertKeypadLettersToDigits(String input)2149     public static String convertKeypadLettersToDigits(String input) {
2150         if (input == null) {
2151             return input;
2152         }
2153         int len = input.length();
2154         if (len == 0) {
2155             return input;
2156         }
2157 
2158         char[] out = input.toCharArray();
2159 
2160         for (int i = 0; i < len; i++) {
2161             char c = out[i];
2162             // If this char isn't in KEYPAD_MAP at all, just leave it alone.
2163             out[i] = (char) KEYPAD_MAP.get(c, c);
2164         }
2165 
2166         return new String(out);
2167     }
2168 
2169     /**
2170      * The phone keypad letter mapping (see ITU E.161 or ISO/IEC 9995-8.)
2171      * TODO: This should come from a resource.
2172      */
2173     private static final SparseIntArray KEYPAD_MAP = new SparseIntArray();
2174     static {
2175         KEYPAD_MAP.put('a', '2'); KEYPAD_MAP.put('b', '2'); KEYPAD_MAP.put('c', '2');
2176         KEYPAD_MAP.put('A', '2'); KEYPAD_MAP.put('B', '2'); KEYPAD_MAP.put('C', '2');
2177 
2178         KEYPAD_MAP.put('d', '3'); KEYPAD_MAP.put('e', '3'); KEYPAD_MAP.put('f', '3');
2179         KEYPAD_MAP.put('D', '3'); KEYPAD_MAP.put('E', '3'); KEYPAD_MAP.put('F', '3');
2180 
2181         KEYPAD_MAP.put('g', '4'); KEYPAD_MAP.put('h', '4'); KEYPAD_MAP.put('i', '4');
2182         KEYPAD_MAP.put('G', '4'); KEYPAD_MAP.put('H', '4'); KEYPAD_MAP.put('I', '4');
2183 
2184         KEYPAD_MAP.put('j', '5'); KEYPAD_MAP.put('k', '5'); KEYPAD_MAP.put('l', '5');
2185         KEYPAD_MAP.put('J', '5'); KEYPAD_MAP.put('K', '5'); KEYPAD_MAP.put('L', '5');
2186 
2187         KEYPAD_MAP.put('m', '6'); KEYPAD_MAP.put('n', '6'); KEYPAD_MAP.put('o', '6');
2188         KEYPAD_MAP.put('M', '6'); KEYPAD_MAP.put('N', '6'); KEYPAD_MAP.put('O', '6');
2189 
2190         KEYPAD_MAP.put('p', '7'); KEYPAD_MAP.put('q', '7'); KEYPAD_MAP.put('r', '7'); KEYPAD_MAP.put('s', '7');
2191         KEYPAD_MAP.put('P', '7'); KEYPAD_MAP.put('Q', '7'); KEYPAD_MAP.put('R', '7'); KEYPAD_MAP.put('S', '7');
2192 
2193         KEYPAD_MAP.put('t', '8'); KEYPAD_MAP.put('u', '8'); KEYPAD_MAP.put('v', '8');
2194         KEYPAD_MAP.put('T', '8'); KEYPAD_MAP.put('U', '8'); KEYPAD_MAP.put('V', '8');
2195 
2196         KEYPAD_MAP.put('w', '9'); KEYPAD_MAP.put('x', '9'); KEYPAD_MAP.put('y', '9'); KEYPAD_MAP.put('z', '9');
2197         KEYPAD_MAP.put('W', '9'); KEYPAD_MAP.put('X', '9'); KEYPAD_MAP.put('Y', '9'); KEYPAD_MAP.put('Z', '9');
2198     }
2199 
2200     //================ Plus Code formatting =========================
2201     private static final char PLUS_SIGN_CHAR = '+';
2202     private static final String PLUS_SIGN_STRING = "+";
2203     private static final String NANP_IDP_STRING = "011";
2204     private static final int NANP_LENGTH = 10;
2205 
2206     /**
2207      * This function checks if there is a plus sign (+) in the passed-in dialing number.
2208      * If there is, it processes the plus sign based on the default telephone
2209      * numbering plan of the system when the phone is activated and the current
2210      * telephone numbering plan of the system that the phone is camped on.
2211      * Currently, we only support the case that the default and current telephone
2212      * numbering plans are North American Numbering Plan(NANP).
2213      *
2214      * The passed-in dialStr should only contain the valid format as described below,
2215      * 1) the 1st character in the dialStr should be one of the really dialable
2216      *    characters listed below
2217      *    ISO-LATIN characters 0-9, *, # , +
2218      * 2) the dialStr should already strip out the separator characters,
2219      *    every character in the dialStr should be one of the non separator characters
2220      *    listed below
2221      *    ISO-LATIN characters 0-9, *, # , +, WILD, WAIT, PAUSE
2222      *
2223      * Otherwise, this function returns the dial string passed in
2224      *
2225      * @param dialStr the original dial string
2226      * @return the converted dial string if the current/default countries belong to NANP,
2227      * and if there is the "+" in the original dial string. Otherwise, the original dial
2228      * string returns.
2229      *
2230      * This API is for CDMA only
2231      *
2232      * @hide TODO: pending API Council approval
2233      */
cdmaCheckAndProcessPlusCode(String dialStr)2234     public static String cdmaCheckAndProcessPlusCode(String dialStr) {
2235         if (!TextUtils.isEmpty(dialStr)) {
2236             if (isReallyDialable(dialStr.charAt(0)) &&
2237                 isNonSeparator(dialStr)) {
2238                 String currIso = TelephonyManager.getDefault().getNetworkCountryIso();
2239                 String defaultIso = TelephonyManager.getDefault().getSimCountryIso();
2240                 if (!TextUtils.isEmpty(currIso) && !TextUtils.isEmpty(defaultIso)) {
2241                     return cdmaCheckAndProcessPlusCodeByNumberFormat(dialStr,
2242                             getFormatTypeFromCountryCode(currIso),
2243                             getFormatTypeFromCountryCode(defaultIso));
2244                 }
2245             }
2246         }
2247         return dialStr;
2248     }
2249 
2250     /**
2251      * Process phone number for CDMA, converting plus code using the home network number format.
2252      * This is used for outgoing SMS messages.
2253      *
2254      * @param dialStr the original dial string
2255      * @return the converted dial string
2256      * @hide for internal use
2257      */
cdmaCheckAndProcessPlusCodeForSms(String dialStr)2258     public static String cdmaCheckAndProcessPlusCodeForSms(String dialStr) {
2259         if (!TextUtils.isEmpty(dialStr)) {
2260             if (isReallyDialable(dialStr.charAt(0)) && isNonSeparator(dialStr)) {
2261                 String defaultIso = TelephonyManager.getDefault().getSimCountryIso();
2262                 if (!TextUtils.isEmpty(defaultIso)) {
2263                     int format = getFormatTypeFromCountryCode(defaultIso);
2264                     return cdmaCheckAndProcessPlusCodeByNumberFormat(dialStr, format, format);
2265                 }
2266             }
2267         }
2268         return dialStr;
2269     }
2270 
2271     /**
2272      * This function should be called from checkAndProcessPlusCode only
2273      * And it is used for test purpose also.
2274      *
2275      * It checks the dial string by looping through the network portion,
2276      * post dial portion 1, post dial porting 2, etc. If there is any
2277      * plus sign, then process the plus sign.
2278      * Currently, this function supports the plus sign conversion within NANP only.
2279      * Specifically, it handles the plus sign in the following ways:
2280      * 1)+1NANP,remove +, e.g.
2281      *   +18475797000 is converted to 18475797000,
2282      * 2)+NANP or +non-NANP Numbers,replace + with the current NANP IDP, e.g,
2283      *   +8475797000 is converted to 0118475797000,
2284      *   +11875767800 is converted to 01111875767800
2285      * 3)+1NANP in post dial string(s), e.g.
2286      *   8475797000;+18475231753 is converted to 8475797000;18475231753
2287      *
2288      *
2289      * @param dialStr the original dial string
2290      * @param currFormat the numbering system of the current country that the phone is camped on
2291      * @param defaultFormat the numbering system of the country that the phone is activated on
2292      * @return the converted dial string if the current/default countries belong to NANP,
2293      * and if there is the "+" in the original dial string. Otherwise, the original dial
2294      * string returns.
2295      *
2296      * @hide
2297      */
2298     public static String
cdmaCheckAndProcessPlusCodeByNumberFormat(String dialStr,int currFormat,int defaultFormat)2299     cdmaCheckAndProcessPlusCodeByNumberFormat(String dialStr,int currFormat,int defaultFormat) {
2300         String retStr = dialStr;
2301 
2302         boolean useNanp = (currFormat == defaultFormat) && (currFormat == FORMAT_NANP);
2303 
2304         // Checks if the plus sign character is in the passed-in dial string
2305         if (dialStr != null &&
2306             dialStr.lastIndexOf(PLUS_SIGN_STRING) != -1) {
2307 
2308             // Handle case where default and current telephone numbering plans are NANP.
2309             String postDialStr = null;
2310             String tempDialStr = dialStr;
2311 
2312             // Sets the retStr to null since the conversion will be performed below.
2313             retStr = null;
2314             if (DBG) log("checkAndProcessPlusCode,dialStr=" + dialStr);
2315             // This routine is to process the plus sign in the dial string by loop through
2316             // the network portion, post dial portion 1, post dial portion 2... etc. if
2317             // applied
2318             do {
2319                 String networkDialStr;
2320                 // Format the string based on the rules for the country the number is from,
2321                 // and the current country the phone is camped
2322                 if (useNanp) {
2323                     networkDialStr = extractNetworkPortion(tempDialStr);
2324                 } else  {
2325                     networkDialStr = extractNetworkPortionAlt(tempDialStr);
2326 
2327                 }
2328 
2329                 networkDialStr = processPlusCode(networkDialStr, useNanp);
2330 
2331                 // Concatenates the string that is converted from network portion
2332                 if (!TextUtils.isEmpty(networkDialStr)) {
2333                     if (retStr == null) {
2334                         retStr = networkDialStr;
2335                     } else {
2336                         retStr = retStr.concat(networkDialStr);
2337                     }
2338                 } else {
2339                     // This should never happen since we checked the if dialStr is null
2340                     // and if it contains the plus sign in the beginning of this function.
2341                     // The plus sign is part of the network portion.
2342                     Rlog.e("checkAndProcessPlusCode: null newDialStr", networkDialStr);
2343                     return dialStr;
2344                 }
2345                 postDialStr = extractPostDialPortion(tempDialStr);
2346                 if (!TextUtils.isEmpty(postDialStr)) {
2347                     int dialableIndex = findDialableIndexFromPostDialStr(postDialStr);
2348 
2349                     // dialableIndex should always be greater than 0
2350                     if (dialableIndex >= 1) {
2351                         retStr = appendPwCharBackToOrigDialStr(dialableIndex,
2352                                  retStr,postDialStr);
2353                         // Skips the P/W character, extracts the dialable portion
2354                         tempDialStr = postDialStr.substring(dialableIndex);
2355                     } else {
2356                         // Non-dialable character such as P/W should not be at the end of
2357                         // the dial string after P/W processing in CdmaConnection.java
2358                         // Set the postDialStr to "" to break out of the loop
2359                         if (dialableIndex < 0) {
2360                             postDialStr = "";
2361                         }
2362                         Rlog.e("wrong postDialStr=", postDialStr);
2363                     }
2364                 }
2365                 if (DBG) log("checkAndProcessPlusCode,postDialStr=" + postDialStr);
2366             } while (!TextUtils.isEmpty(postDialStr) && !TextUtils.isEmpty(tempDialStr));
2367         }
2368         return retStr;
2369     }
2370 
2371     /**
2372      * Wrap the supplied {@code CharSequence} with a {@code TtsSpan}, annotating it as
2373      * containing a phone number in its entirety.
2374      *
2375      * @param phoneNumber A {@code CharSequence} the entirety of which represents a phone number.
2376      * @return A {@code CharSequence} with appropriate annotations.
2377      */
createTtsSpannable(CharSequence phoneNumber)2378     public static CharSequence createTtsSpannable(CharSequence phoneNumber) {
2379         if (phoneNumber == null) {
2380             return null;
2381         }
2382         Spannable spannable = Spannable.Factory.getInstance().newSpannable(phoneNumber);
2383         PhoneNumberUtils.addTtsSpan(spannable, 0, spannable.length());
2384         return spannable;
2385     }
2386 
2387     /**
2388      * Attach a {@link TtsSpan} to the supplied {@code Spannable} at the indicated location,
2389      * annotating that location as containing a phone number.
2390      *
2391      * @param s A {@code Spannable} to annotate.
2392      * @param start The starting character position of the phone number in {@code s}.
2393      * @param endExclusive The position after the ending character in the phone number {@code s}.
2394      */
addTtsSpan(Spannable s, int start, int endExclusive)2395     public static void addTtsSpan(Spannable s, int start, int endExclusive) {
2396         s.setSpan(createTtsSpan(s.subSequence(start, endExclusive).toString()),
2397                 start,
2398                 endExclusive,
2399                 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
2400     }
2401 
2402     /**
2403      * Wrap the supplied {@code CharSequence} with a {@code TtsSpan}, annotating it as
2404      * containing a phone number in its entirety.
2405      *
2406      * @param phoneNumber A {@code CharSequence} the entirety of which represents a phone number.
2407      * @return A {@code CharSequence} with appropriate annotations.
2408      * @deprecated Renamed {@link #createTtsSpannable}.
2409      *
2410      * @hide
2411      */
2412     @Deprecated
ttsSpanAsPhoneNumber(CharSequence phoneNumber)2413     public static CharSequence ttsSpanAsPhoneNumber(CharSequence phoneNumber) {
2414         return createTtsSpannable(phoneNumber);
2415     }
2416 
2417     /**
2418      * Attach a {@link TtsSpan} to the supplied {@code Spannable} at the indicated location,
2419      * annotating that location as containing a phone number.
2420      *
2421      * @param s A {@code Spannable} to annotate.
2422      * @param start The starting character position of the phone number in {@code s}.
2423      * @param end The ending character position of the phone number in {@code s}.
2424      *
2425      * @deprecated Renamed {@link #addTtsSpan}.
2426      *
2427      * @hide
2428      */
2429     @Deprecated
ttsSpanAsPhoneNumber(Spannable s, int start, int end)2430     public static void ttsSpanAsPhoneNumber(Spannable s, int start, int end) {
2431         addTtsSpan(s, start, end);
2432     }
2433 
2434     /**
2435      * Create a {@code TtsSpan} for the supplied {@code String}.
2436      *
2437      * @param phoneNumberString A {@code String} the entirety of which represents a phone number.
2438      * @return A {@code TtsSpan} for {@param phoneNumberString}.
2439      */
createTtsSpan(String phoneNumberString)2440     public static TtsSpan createTtsSpan(String phoneNumberString) {
2441         if (phoneNumberString == null) {
2442             return null;
2443         }
2444 
2445         // Parse the phone number
2446         final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance();
2447         PhoneNumber phoneNumber = null;
2448         try {
2449             // Don't supply a defaultRegion so this fails for non-international numbers because
2450             // we don't want to TalkBalk to read a country code (e.g. +1) if it is not already
2451             // present
2452             phoneNumber = phoneNumberUtil.parse(phoneNumberString, /* defaultRegion */ null);
2453         } catch (NumberParseException ignored) {
2454         }
2455 
2456         // Build a telephone tts span
2457         final TtsSpan.TelephoneBuilder builder = new TtsSpan.TelephoneBuilder();
2458         if (phoneNumber == null) {
2459             // Strip separators otherwise TalkBack will be silent
2460             // (this behavior was observed with TalkBalk 4.0.2 from their alpha channel)
2461             builder.setNumberParts(splitAtNonNumerics(phoneNumberString));
2462         } else {
2463             if (phoneNumber.hasCountryCode()) {
2464                 builder.setCountryCode(Integer.toString(phoneNumber.getCountryCode()));
2465             }
2466             builder.setNumberParts(Long.toString(phoneNumber.getNationalNumber()));
2467         }
2468         return builder.build();
2469     }
2470 
2471     // Split a phone number like "+20(123)-456#" using spaces, ignoring anything that is not
2472     // a digit, to produce a result like "20 123 456".
splitAtNonNumerics(CharSequence number)2473     private static String splitAtNonNumerics(CharSequence number) {
2474         StringBuilder sb = new StringBuilder(number.length());
2475         for (int i = 0; i < number.length(); i++) {
2476             sb.append(PhoneNumberUtils.isISODigit(number.charAt(i))
2477                     ? number.charAt(i)
2478                     : " ");
2479         }
2480         // It is very important to remove extra spaces. At time of writing, any leading or trailing
2481         // spaces, or any sequence of more than one space, will confuse TalkBack and cause the TTS
2482         // span to be non-functional!
2483         return sb.toString().replaceAll(" +", " ").trim();
2484     }
2485 
getCurrentIdp(boolean useNanp)2486     private static String getCurrentIdp(boolean useNanp) {
2487         String ps = null;
2488         if (useNanp) {
2489             ps = NANP_IDP_STRING;
2490         } else {
2491             // in case, there is no IDD is found, we shouldn't convert it.
2492             ps = SystemProperties.get(PROPERTY_OPERATOR_IDP_STRING, PLUS_SIGN_STRING);
2493         }
2494         return ps;
2495     }
2496 
isTwoToNine(char c)2497     private static boolean isTwoToNine (char c) {
2498         if (c >= '2' && c <= '9') {
2499             return true;
2500         } else {
2501             return false;
2502         }
2503     }
2504 
getFormatTypeFromCountryCode(String country)2505     private static int getFormatTypeFromCountryCode (String country) {
2506         // Check for the NANP countries
2507         int length = NANP_COUNTRIES.length;
2508         for (int i = 0; i < length; i++) {
2509             if (NANP_COUNTRIES[i].compareToIgnoreCase(country) == 0) {
2510                 return FORMAT_NANP;
2511             }
2512         }
2513         if ("jp".compareToIgnoreCase(country) == 0) {
2514             return FORMAT_JAPAN;
2515         }
2516         return FORMAT_UNKNOWN;
2517     }
2518 
2519     /**
2520      * This function checks if the passed in string conforms to the NANP format
2521      * i.e. NXX-NXX-XXXX, N is any digit 2-9 and X is any digit 0-9
2522      * @hide
2523      */
isNanp(String dialStr)2524     public static boolean isNanp (String dialStr) {
2525         boolean retVal = false;
2526         if (dialStr != null) {
2527             if (dialStr.length() == NANP_LENGTH) {
2528                 if (isTwoToNine(dialStr.charAt(0)) &&
2529                     isTwoToNine(dialStr.charAt(3))) {
2530                     retVal = true;
2531                     for (int i=1; i<NANP_LENGTH; i++ ) {
2532                         char c=dialStr.charAt(i);
2533                         if (!PhoneNumberUtils.isISODigit(c)) {
2534                             retVal = false;
2535                             break;
2536                         }
2537                     }
2538                 }
2539             }
2540         } else {
2541             Rlog.e("isNanp: null dialStr passed in", dialStr);
2542         }
2543         return retVal;
2544     }
2545 
2546    /**
2547     * This function checks if the passed in string conforms to 1-NANP format
2548     */
isOneNanp(String dialStr)2549     private static boolean isOneNanp(String dialStr) {
2550         boolean retVal = false;
2551         if (dialStr != null) {
2552             String newDialStr = dialStr.substring(1);
2553             if ((dialStr.charAt(0) == '1') && isNanp(newDialStr)) {
2554                 retVal = true;
2555             }
2556         } else {
2557             Rlog.e("isOneNanp: null dialStr passed in", dialStr);
2558         }
2559         return retVal;
2560     }
2561 
2562     /**
2563      * Determines if the specified number is actually a URI
2564      * (i.e. a SIP address) rather than a regular PSTN phone number,
2565      * based on whether or not the number contains an "@" character.
2566      *
2567      * @hide
2568      * @param number
2569      * @return true if number contains @
2570      */
isUriNumber(String number)2571     public static boolean isUriNumber(String number) {
2572         // Note we allow either "@" or "%40" to indicate a URI, in case
2573         // the passed-in string is URI-escaped.  (Neither "@" nor "%40"
2574         // will ever be found in a legal PSTN number.)
2575         return number != null && (number.contains("@") || number.contains("%40"));
2576     }
2577 
2578     /**
2579      * @return the "username" part of the specified SIP address,
2580      *         i.e. the part before the "@" character (or "%40").
2581      *
2582      * @param number SIP address of the form "username@domainname"
2583      *               (or the URI-escaped equivalent "username%40domainname")
2584      * @see #isUriNumber
2585      *
2586      * @hide
2587      */
getUsernameFromUriNumber(String number)2588     public static String getUsernameFromUriNumber(String number) {
2589         // The delimiter between username and domain name can be
2590         // either "@" or "%40" (the URI-escaped equivalent.)
2591         int delimiterIndex = number.indexOf('@');
2592         if (delimiterIndex < 0) {
2593             delimiterIndex = number.indexOf("%40");
2594         }
2595         if (delimiterIndex < 0) {
2596             Rlog.w(LOG_TAG,
2597                   "getUsernameFromUriNumber: no delimiter found in SIP addr '" + number + "'");
2598             delimiterIndex = number.length();
2599         }
2600         return number.substring(0, delimiterIndex);
2601     }
2602 
2603     /**
2604      * This function handles the plus code conversion
2605      * If the number format is
2606      * 1)+1NANP,remove +,
2607      * 2)other than +1NANP, any + numbers,replace + with the current IDP
2608      */
processPlusCode(String networkDialStr, boolean useNanp)2609     private static String processPlusCode(String networkDialStr, boolean useNanp) {
2610         String retStr = networkDialStr;
2611 
2612         if (DBG) log("processPlusCode, networkDialStr = " + networkDialStr
2613                 + "for NANP = " + useNanp);
2614         // If there is a plus sign at the beginning of the dial string,
2615         // Convert the plus sign to the default IDP since it's an international number
2616         if (networkDialStr != null &&
2617             networkDialStr.charAt(0) == PLUS_SIGN_CHAR &&
2618             networkDialStr.length() > 1) {
2619             String newStr = networkDialStr.substring(1);
2620             // TODO: for nonNanp, should the '+' be removed if following number is country code
2621             if (useNanp && isOneNanp(newStr)) {
2622                 // Remove the leading plus sign
2623                 retStr = newStr;
2624             } else {
2625                 // Replaces the plus sign with the default IDP
2626                 retStr = networkDialStr.replaceFirst("[+]", getCurrentIdp(useNanp));
2627             }
2628         }
2629         if (DBG) log("processPlusCode, retStr=" + retStr);
2630         return retStr;
2631     }
2632 
2633     // This function finds the index of the dialable character(s)
2634     // in the post dial string
findDialableIndexFromPostDialStr(String postDialStr)2635     private static int findDialableIndexFromPostDialStr(String postDialStr) {
2636         for (int index = 0;index < postDialStr.length();index++) {
2637              char c = postDialStr.charAt(index);
2638              if (isReallyDialable(c)) {
2639                 return index;
2640              }
2641         }
2642         return -1;
2643     }
2644 
2645     // This function appends the non-dialable P/W character to the original
2646     // dial string based on the dialable index passed in
2647     private static String
appendPwCharBackToOrigDialStr(int dialableIndex,String origStr, String dialStr)2648     appendPwCharBackToOrigDialStr(int dialableIndex,String origStr, String dialStr) {
2649         String retStr;
2650 
2651         // There is only 1 P/W character before the dialable characters
2652         if (dialableIndex == 1) {
2653             StringBuilder ret = new StringBuilder(origStr);
2654             ret = ret.append(dialStr.charAt(0));
2655             retStr = ret.toString();
2656         } else {
2657             // It means more than 1 P/W characters in the post dial string,
2658             // appends to retStr
2659             String nonDigitStr = dialStr.substring(0,dialableIndex);
2660             retStr = origStr.concat(nonDigitStr);
2661         }
2662         return retStr;
2663     }
2664 
2665     //===== Beginning of utility methods used in compareLoosely() =====
2666 
2667     /**
2668      * Phone numbers are stored in "lookup" form in the database
2669      * as reversed strings to allow for caller ID lookup
2670      *
2671      * This method takes a phone number and makes a valid SQL "LIKE"
2672      * string that will match the lookup form
2673      *
2674      */
2675     /** all of a up to len must be an international prefix or
2676      *  separators/non-dialing digits
2677      */
2678     private static boolean
matchIntlPrefix(String a, int len)2679     matchIntlPrefix(String a, int len) {
2680         /* '([^0-9*#+pwn]\+[^0-9*#+pwn] | [^0-9*#+pwn]0(0|11)[^0-9*#+pwn] )$' */
2681         /*        0       1                           2 3 45               */
2682 
2683         int state = 0;
2684         for (int i = 0 ; i < len ; i++) {
2685             char c = a.charAt(i);
2686 
2687             switch (state) {
2688                 case 0:
2689                     if      (c == '+') state = 1;
2690                     else if (c == '0') state = 2;
2691                     else if (isNonSeparator(c)) return false;
2692                 break;
2693 
2694                 case 2:
2695                     if      (c == '0') state = 3;
2696                     else if (c == '1') state = 4;
2697                     else if (isNonSeparator(c)) return false;
2698                 break;
2699 
2700                 case 4:
2701                     if      (c == '1') state = 5;
2702                     else if (isNonSeparator(c)) return false;
2703                 break;
2704 
2705                 default:
2706                     if (isNonSeparator(c)) return false;
2707                 break;
2708 
2709             }
2710         }
2711 
2712         return state == 1 || state == 3 || state == 5;
2713     }
2714 
2715     /** all of 'a' up to len must be a (+|00|011)country code)
2716      *  We're fast and loose with the country code. Any \d{1,3} matches */
2717     private static boolean
matchIntlPrefixAndCC(String a, int len)2718     matchIntlPrefixAndCC(String a, int len) {
2719         /*  [^0-9*#+pwn]*(\+|0(0|11)\d\d?\d? [^0-9*#+pwn] $ */
2720         /*      0          1 2 3 45  6 7  8                 */
2721 
2722         int state = 0;
2723         for (int i = 0 ; i < len ; i++ ) {
2724             char c = a.charAt(i);
2725 
2726             switch (state) {
2727                 case 0:
2728                     if      (c == '+') state = 1;
2729                     else if (c == '0') state = 2;
2730                     else if (isNonSeparator(c)) return false;
2731                 break;
2732 
2733                 case 2:
2734                     if      (c == '0') state = 3;
2735                     else if (c == '1') state = 4;
2736                     else if (isNonSeparator(c)) return false;
2737                 break;
2738 
2739                 case 4:
2740                     if      (c == '1') state = 5;
2741                     else if (isNonSeparator(c)) return false;
2742                 break;
2743 
2744                 case 1:
2745                 case 3:
2746                 case 5:
2747                     if      (isISODigit(c)) state = 6;
2748                     else if (isNonSeparator(c)) return false;
2749                 break;
2750 
2751                 case 6:
2752                 case 7:
2753                     if      (isISODigit(c)) state++;
2754                     else if (isNonSeparator(c)) return false;
2755                 break;
2756 
2757                 default:
2758                     if (isNonSeparator(c)) return false;
2759             }
2760         }
2761 
2762         return state == 6 || state == 7 || state == 8;
2763     }
2764 
2765     /** all of 'a' up to len must match non-US trunk prefix ('0') */
2766     private static boolean
matchTrunkPrefix(String a, int len)2767     matchTrunkPrefix(String a, int len) {
2768         boolean found;
2769 
2770         found = false;
2771 
2772         for (int i = 0 ; i < len ; i++) {
2773             char c = a.charAt(i);
2774 
2775             if (c == '0' && !found) {
2776                 found = true;
2777             } else if (isNonSeparator(c)) {
2778                 return false;
2779             }
2780         }
2781 
2782         return found;
2783     }
2784 
2785     //===== End of utility methods used only in compareLoosely() =====
2786 
2787     //===== Beginning of utility methods used only in compareStrictly() ====
2788 
2789     /*
2790      * If true, the number is country calling code.
2791      */
2792     private static final boolean COUNTRY_CALLING_CALL[] = {
2793         true, true, false, false, false, false, false, true, false, false,
2794         false, false, false, false, false, false, false, false, false, false,
2795         true, false, false, false, false, false, false, true, true, false,
2796         true, true, true, true, true, false, true, false, false, true,
2797         true, false, false, true, true, true, true, true, true, true,
2798         false, true, true, true, true, true, true, true, true, false,
2799         true, true, true, true, true, true, true, false, false, false,
2800         false, false, false, false, false, false, false, false, false, false,
2801         false, true, true, true, true, false, true, false, false, true,
2802         true, true, true, true, true, true, false, false, true, false,
2803     };
2804     private static final int CCC_LENGTH = COUNTRY_CALLING_CALL.length;
2805 
2806     /**
2807      * @return true when input is valid Country Calling Code.
2808      */
isCountryCallingCode(int countryCallingCodeCandidate)2809     private static boolean isCountryCallingCode(int countryCallingCodeCandidate) {
2810         return countryCallingCodeCandidate > 0 && countryCallingCodeCandidate < CCC_LENGTH &&
2811                 COUNTRY_CALLING_CALL[countryCallingCodeCandidate];
2812     }
2813 
2814     /**
2815      * Returns integer corresponding to the input if input "ch" is
2816      * ISO-LATIN characters 0-9.
2817      * Returns -1 otherwise
2818      */
tryGetISODigit(char ch)2819     private static int tryGetISODigit(char ch) {
2820         if ('0' <= ch && ch <= '9') {
2821             return ch - '0';
2822         } else {
2823             return -1;
2824         }
2825     }
2826 
2827     private static class CountryCallingCodeAndNewIndex {
2828         public final int countryCallingCode;
2829         public final int newIndex;
CountryCallingCodeAndNewIndex(int countryCode, int newIndex)2830         public CountryCallingCodeAndNewIndex(int countryCode, int newIndex) {
2831             this.countryCallingCode = countryCode;
2832             this.newIndex = newIndex;
2833         }
2834     }
2835 
2836     /*
2837      * Note that this function does not strictly care the country calling code with
2838      * 3 length (like Morocco: +212), assuming it is enough to use the first two
2839      * digit to compare two phone numbers.
2840      */
tryGetCountryCallingCodeAndNewIndex( String str, boolean acceptThailandCase)2841     private static CountryCallingCodeAndNewIndex tryGetCountryCallingCodeAndNewIndex(
2842         String str, boolean acceptThailandCase) {
2843         // Rough regexp:
2844         //  ^[^0-9*#+]*((\+|0(0|11)\d\d?|166) [^0-9*#+] $
2845         //         0        1 2 3 45  6 7  89
2846         //
2847         // In all the states, this function ignores separator characters.
2848         // "166" is the special case for the call from Thailand to the US. Uguu!
2849         int state = 0;
2850         int ccc = 0;
2851         final int length = str.length();
2852         for (int i = 0 ; i < length ; i++ ) {
2853             char ch = str.charAt(i);
2854             switch (state) {
2855                 case 0:
2856                     if      (ch == '+') state = 1;
2857                     else if (ch == '0') state = 2;
2858                     else if (ch == '1') {
2859                         if (acceptThailandCase) {
2860                             state = 8;
2861                         } else {
2862                             return null;
2863                         }
2864                     } else if (isDialable(ch)) {
2865                         return null;
2866                     }
2867                 break;
2868 
2869                 case 2:
2870                     if      (ch == '0') state = 3;
2871                     else if (ch == '1') state = 4;
2872                     else if (isDialable(ch)) {
2873                         return null;
2874                     }
2875                 break;
2876 
2877                 case 4:
2878                     if      (ch == '1') state = 5;
2879                     else if (isDialable(ch)) {
2880                         return null;
2881                     }
2882                 break;
2883 
2884                 case 1:
2885                 case 3:
2886                 case 5:
2887                 case 6:
2888                 case 7:
2889                     {
2890                         int ret = tryGetISODigit(ch);
2891                         if (ret > 0) {
2892                             ccc = ccc * 10 + ret;
2893                             if (ccc >= 100 || isCountryCallingCode(ccc)) {
2894                                 return new CountryCallingCodeAndNewIndex(ccc, i + 1);
2895                             }
2896                             if (state == 1 || state == 3 || state == 5) {
2897                                 state = 6;
2898                             } else {
2899                                 state++;
2900                             }
2901                         } else if (isDialable(ch)) {
2902                             return null;
2903                         }
2904                     }
2905                     break;
2906                 case 8:
2907                     if (ch == '6') state = 9;
2908                     else if (isDialable(ch)) {
2909                         return null;
2910                     }
2911                     break;
2912                 case 9:
2913                     if (ch == '6') {
2914                         return new CountryCallingCodeAndNewIndex(66, i + 1);
2915                     } else {
2916                         return null;
2917                     }
2918                 default:
2919                     return null;
2920             }
2921         }
2922 
2923         return null;
2924     }
2925 
2926     /**
2927      * Currently this function simply ignore the first digit assuming it is
2928      * trunk prefix. Actually trunk prefix is different in each country.
2929      *
2930      * e.g.
2931      * "+79161234567" equals "89161234567" (Russian trunk digit is 8)
2932      * "+33123456789" equals "0123456789" (French trunk digit is 0)
2933      *
2934      */
tryGetTrunkPrefixOmittedIndex(String str, int currentIndex)2935     private static int tryGetTrunkPrefixOmittedIndex(String str, int currentIndex) {
2936         int length = str.length();
2937         for (int i = currentIndex ; i < length ; i++) {
2938             final char ch = str.charAt(i);
2939             if (tryGetISODigit(ch) >= 0) {
2940                 return i + 1;
2941             } else if (isDialable(ch)) {
2942                 return -1;
2943             }
2944         }
2945         return -1;
2946     }
2947 
2948     /**
2949      * Return true if the prefix of "str" is "ignorable". Here, "ignorable" means
2950      * that "str" has only one digit and separator characters. The one digit is
2951      * assumed to be trunk prefix.
2952      */
checkPrefixIsIgnorable(final String str, int forwardIndex, int backwardIndex)2953     private static boolean checkPrefixIsIgnorable(final String str,
2954             int forwardIndex, int backwardIndex) {
2955         boolean trunk_prefix_was_read = false;
2956         while (backwardIndex >= forwardIndex) {
2957             if (tryGetISODigit(str.charAt(backwardIndex)) >= 0) {
2958                 if (trunk_prefix_was_read) {
2959                     // More than one digit appeared, meaning that "a" and "b"
2960                     // is different.
2961                     return false;
2962                 } else {
2963                     // Ignore just one digit, assuming it is trunk prefix.
2964                     trunk_prefix_was_read = true;
2965                 }
2966             } else if (isDialable(str.charAt(backwardIndex))) {
2967                 // Trunk prefix is a digit, not "*", "#"...
2968                 return false;
2969             }
2970             backwardIndex--;
2971         }
2972 
2973         return true;
2974     }
2975 
2976     /**
2977      * Returns Default voice subscription Id.
2978      */
getDefaultVoiceSubId()2979     private static int getDefaultVoiceSubId() {
2980         return SubscriptionManager.getDefaultVoiceSubId();
2981     }
2982     //==== End of utility methods used only in compareStrictly() =====
2983 }
2984