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