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