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