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