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