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