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