PhoneNumberUtils.java revision db1f4993989bb03cae2f3e008d4db3e49577aa73
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 android.content.Context;
20import android.content.Intent;
21import android.database.Cursor;
22import android.net.Uri;
23import android.os.SystemProperties;
24import android.provider.Contacts;
25import android.provider.ContactsContract;
26import android.text.Editable;
27import android.text.SpannableStringBuilder;
28import android.text.TextUtils;
29import android.util.Log;
30import android.util.SparseIntArray;
31
32import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_ISO_COUNTRY;
33import static com.android.internal.telephony.TelephonyProperties.PROPERTY_IDP_STRING;
34import static com.android.internal.telephony.TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY;
35
36import java.util.Locale;
37import java.util.regex.Matcher;
38import java.util.regex.Pattern;
39
40/**
41 * Various utilities for dealing with phone number strings.
42 */
43public class PhoneNumberUtils
44{
45    /*
46     * Special characters
47     *
48     * (See "What is a phone number?" doc)
49     * 'p' --- GSM pause character, same as comma
50     * 'n' --- GSM wild character
51     * 'w' --- GSM wait character
52     */
53    public static final char PAUSE = ',';
54    public static final char WAIT = ';';
55    public static final char WILD = 'N';
56
57    /*
58     * TOA = TON + NPI
59     * See TS 24.008 section 10.5.4.7 for details.
60     * These are the only really useful TOA values
61     */
62    public static final int TOA_International = 0x91;
63    public static final int TOA_Unknown = 0x81;
64
65    static final String LOG_TAG = "PhoneNumberUtils";
66    private static final boolean DBG = false;
67
68    /*
69     * global-phone-number = ["+"] 1*( DIGIT / written-sep )
70     * written-sep         = ("-"/".")
71     */
72    private static final Pattern GLOBAL_PHONE_NUMBER_PATTERN =
73            Pattern.compile("[\\+]?[0-9.-]+");
74
75    /** True if c is ISO-LATIN characters 0-9 */
76    public static boolean
77    isISODigit (char c) {
78        return c >= '0' && c <= '9';
79    }
80
81    /** True if c is ISO-LATIN characters 0-9, *, # */
82    public final static boolean
83    is12Key(char c) {
84        return (c >= '0' && c <= '9') || c == '*' || c == '#';
85    }
86
87    /** True if c is ISO-LATIN characters 0-9, *, # , +, WILD  */
88    public final static boolean
89    isDialable(char c) {
90        return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+' || c == WILD;
91    }
92
93    /** True if c is ISO-LATIN characters 0-9, *, # , + (no WILD)  */
94    public final static boolean
95    isReallyDialable(char c) {
96        return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+';
97    }
98
99    /** True if c is ISO-LATIN characters 0-9, *, # , +, WILD, WAIT, PAUSE   */
100    public final static boolean
101    isNonSeparator(char c) {
102        return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+'
103                || c == WILD || c == WAIT || c == PAUSE;
104    }
105
106    /** This any anything to the right of this char is part of the
107     *  post-dial string (eg this is PAUSE or WAIT)
108     */
109    public final static boolean
110    isStartsPostDial (char c) {
111        return c == PAUSE || c == WAIT;
112    }
113
114    /** Returns true if ch is not dialable or alpha char */
115    private static boolean isSeparator(char ch) {
116        return !isDialable(ch) && !(('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z'));
117    }
118
119    /** Extracts the phone number from an Intent.
120     *
121     * @param intent the intent to get the number of
122     * @param context a context to use for database access
123     *
124     * @return the phone number that would be called by the intent, or
125     *         <code>null</code> if the number cannot be found.
126     */
127    public static String getNumberFromIntent(Intent intent, Context context) {
128        String number = null;
129
130        Uri uri = intent.getData();
131        String scheme = uri.getScheme();
132
133        if (scheme.equals("tel")) {
134            return uri.getSchemeSpecificPart();
135        }
136
137        // TODO: We don't check for SecurityException here (requires
138        // READ_PHONE_STATE permission).
139        if (scheme.equals("voicemail")) {
140            return TelephonyManager.getDefault().getVoiceMailNumber();
141        }
142
143        if (context == null) {
144            return null;
145        }
146
147        String type = intent.resolveType(context);
148        String phoneColumn = null;
149
150        // Correctly read out the phone entry based on requested provider
151        final String authority = uri.getAuthority();
152        if (Contacts.AUTHORITY.equals(authority)) {
153            phoneColumn = Contacts.People.Phones.NUMBER;
154        } else if (ContactsContract.AUTHORITY.equals(authority)) {
155            phoneColumn = ContactsContract.CommonDataKinds.Phone.NUMBER;
156        }
157
158        final Cursor c = context.getContentResolver().query(uri, new String[] {
159            phoneColumn
160        }, null, null, null);
161        if (c != null) {
162            try {
163                if (c.moveToFirst()) {
164                    number = c.getString(c.getColumnIndex(phoneColumn));
165                }
166            } finally {
167                c.close();
168            }
169        }
170
171        return number;
172    }
173
174    /** Extracts the network address portion and canonicalizes
175     *  (filters out separators.)
176     *  Network address portion is everything up to DTMF control digit
177     *  separators (pause or wait), but without non-dialable characters.
178     *
179     *  Please note that the GSM wild character is allowed in the result.
180     *  This must be resolved before dialing.
181     *
182     *  Allows + only in the first  position in the result string.
183     *
184     *  Returns null if phoneNumber == null
185     */
186    public static String
187    extractNetworkPortion(String phoneNumber) {
188        if (phoneNumber == null) {
189            return null;
190        }
191
192        int len = phoneNumber.length();
193        StringBuilder ret = new StringBuilder(len);
194        boolean firstCharAdded = false;
195
196        for (int i = 0; i < len; i++) {
197            char c = phoneNumber.charAt(i);
198            if (isDialable(c) && (c != '+' || !firstCharAdded)) {
199                firstCharAdded = true;
200                ret.append(c);
201            } else if (isStartsPostDial (c)) {
202                break;
203            }
204        }
205
206        return ret.toString();
207    }
208
209    /**
210     * Strips separators from a phone number string.
211     * @param phoneNumber phone number to strip.
212     * @return phone string stripped of separators.
213     */
214    public static String stripSeparators(String phoneNumber) {
215        if (phoneNumber == null) {
216            return null;
217        }
218        int len = phoneNumber.length();
219        StringBuilder ret = new StringBuilder(len);
220
221        for (int i = 0; i < len; i++) {
222            char c = phoneNumber.charAt(i);
223            if (isNonSeparator(c)) {
224                ret.append(c);
225            }
226        }
227
228        return ret.toString();
229    }
230
231    /** or -1 if both are negative */
232    static private int
233    minPositive (int a, int b) {
234        if (a >= 0 && b >= 0) {
235            return (a < b) ? a : b;
236        } else if (a >= 0) { /* && b < 0 */
237            return a;
238        } else if (b >= 0) { /* && a < 0 */
239            return b;
240        } else { /* a < 0 && b < 0 */
241            return -1;
242        }
243    }
244
245    private static void log(String msg) {
246        Log.d(LOG_TAG, msg);
247    }
248    /** index of the last character of the network portion
249     *  (eg anything after is a post-dial string)
250     */
251    static private int
252    indexOfLastNetworkChar(String a) {
253        int pIndex, wIndex;
254        int origLength;
255        int trimIndex;
256
257        origLength = a.length();
258
259        pIndex = a.indexOf(PAUSE);
260        wIndex = a.indexOf(WAIT);
261
262        trimIndex = minPositive(pIndex, wIndex);
263
264        if (trimIndex < 0) {
265            return origLength - 1;
266        } else {
267            return trimIndex - 1;
268        }
269    }
270
271    /**
272     * Extracts the post-dial sequence of DTMF control digits, pauses, and
273     * waits. Strips separators. This string may be empty, but will not be null
274     * unless phoneNumber == null.
275     *
276     * Returns null if phoneNumber == null
277     */
278
279    public static String
280    extractPostDialPortion(String phoneNumber) {
281        if (phoneNumber == null) return null;
282
283        int trimIndex;
284        StringBuilder ret = new StringBuilder();
285
286        trimIndex = indexOfLastNetworkChar (phoneNumber);
287
288        for (int i = trimIndex + 1, s = phoneNumber.length()
289                ; i < s; i++
290        ) {
291            char c = phoneNumber.charAt(i);
292            if (isNonSeparator(c)) {
293                ret.append(c);
294            }
295        }
296
297        return ret.toString();
298    }
299
300    /**
301     * Compare phone numbers a and b, return true if they're identical enough for caller ID purposes.
302     */
303    public static boolean compare(String a, String b) {
304        // We've used loose comparation at least Eclair, which may change in the future.
305
306        return compare(a, b, false);
307    }
308
309    /**
310     * Compare phone numbers a and b, and return true if they're identical
311     * enough for caller ID purposes. Checks a resource to determine whether
312     * to use a strict or loose comparison algorithm.
313     */
314    public static boolean compare(Context context, String a, String b) {
315        boolean useStrict = context.getResources().getBoolean(
316               com.android.internal.R.bool.config_use_strict_phone_number_comparation);
317        return compare(a, b, useStrict);
318    }
319
320    /**
321     * @hide only for testing.
322     */
323    public static boolean compare(String a, String b, boolean useStrictComparation) {
324        return (useStrictComparation ? compareStrictly(a, b) : compareLoosely(a, b));
325    }
326
327    /**
328     * Compare phone numbers a and b, return true if they're identical
329     * enough for caller ID purposes.
330     *
331     * - Compares from right to left
332     * - requires MIN_MATCH (7) characters to match
333     * - handles common trunk prefixes and international prefixes
334     *   (basically, everything except the Russian trunk prefix)
335     *
336     * Note that this method does not return false even when the two phone numbers
337     * are not exactly same; rather; we can call this method "similar()", not "equals()".
338     *
339     * @hide
340     */
341    public static boolean
342    compareLoosely(String a, String b) {
343        int ia, ib;
344        int matched;
345
346        if (a == null || b == null) return a == b;
347
348        if (a.length() == 0 || b.length() == 0) {
349            return false;
350        }
351
352        ia = indexOfLastNetworkChar (a);
353        ib = indexOfLastNetworkChar (b);
354        matched = 0;
355
356        while (ia >= 0 && ib >=0) {
357            char ca, cb;
358            boolean skipCmp = false;
359
360            ca = a.charAt(ia);
361
362            if (!isDialable(ca)) {
363                ia--;
364                skipCmp = true;
365            }
366
367            cb = b.charAt(ib);
368
369            if (!isDialable(cb)) {
370                ib--;
371                skipCmp = true;
372            }
373
374            if (!skipCmp) {
375                if (cb != ca && ca != WILD && cb != WILD) {
376                    break;
377                }
378                ia--; ib--; matched++;
379            }
380        }
381
382        if (matched < MIN_MATCH) {
383            int aLen = a.length();
384
385            // if the input strings match, but their lengths < MIN_MATCH,
386            // treat them as equal.
387            if (aLen == b.length() && aLen == matched) {
388                return true;
389            }
390            return false;
391        }
392
393        // At least one string has matched completely;
394        if (matched >= MIN_MATCH && (ia < 0 || ib < 0)) {
395            return true;
396        }
397
398        /*
399         * Now, what remains must be one of the following for a
400         * match:
401         *
402         *  - a '+' on one and a '00' or a '011' on the other
403         *  - a '0' on one and a (+,00)<country code> on the other
404         *     (for this, a '0' and a '00' prefix would have succeeded above)
405         */
406
407        if (matchIntlPrefix(a, ia + 1)
408            && matchIntlPrefix (b, ib +1)
409        ) {
410            return true;
411        }
412
413        if (matchTrunkPrefix(a, ia + 1)
414            && matchIntlPrefixAndCC(b, ib +1)
415        ) {
416            return true;
417        }
418
419        if (matchTrunkPrefix(b, ib + 1)
420            && matchIntlPrefixAndCC(a, ia +1)
421        ) {
422            return true;
423        }
424
425        return false;
426    }
427
428    /**
429     * @hide
430     */
431    public static boolean
432    compareStrictly(String a, String b) {
433        return compareStrictly(a, b, true);
434    }
435
436    /**
437     * @hide
438     */
439    public static boolean
440    compareStrictly(String a, String b, boolean acceptInvalidCCCPrefix) {
441        if (a == null || b == null) {
442            return a == b;
443        } else if (a.length() == 0 && b.length() == 0) {
444            return false;
445        }
446
447        int forwardIndexA = 0;
448        int forwardIndexB = 0;
449
450        CountryCallingCodeAndNewIndex cccA =
451            tryGetCountryCallingCodeAndNewIndex(a, acceptInvalidCCCPrefix);
452        CountryCallingCodeAndNewIndex cccB =
453            tryGetCountryCallingCodeAndNewIndex(b, acceptInvalidCCCPrefix);
454        boolean bothHasCountryCallingCode = false;
455        boolean okToIgnorePrefix = true;
456        boolean trunkPrefixIsOmittedA = false;
457        boolean trunkPrefixIsOmittedB = false;
458        if (cccA != null && cccB != null) {
459            if (cccA.countryCallingCode != cccB.countryCallingCode) {
460                // Different Country Calling Code. Must be different phone number.
461                return false;
462            }
463            // When both have ccc, do not ignore trunk prefix. Without this,
464            // "+81123123" becomes same as "+810123123" (+81 == Japan)
465            okToIgnorePrefix = false;
466            bothHasCountryCallingCode = true;
467            forwardIndexA = cccA.newIndex;
468            forwardIndexB = cccB.newIndex;
469        } else if (cccA == null && cccB == null) {
470            // When both do not have ccc, do not ignore trunk prefix. Without this,
471            // "123123" becomes same as "0123123"
472            okToIgnorePrefix = false;
473        } else {
474            if (cccA != null) {
475                forwardIndexA = cccA.newIndex;
476            } else {
477                int tmp = tryGetTrunkPrefixOmittedIndex(b, 0);
478                if (tmp >= 0) {
479                    forwardIndexA = tmp;
480                    trunkPrefixIsOmittedA = true;
481                }
482            }
483            if (cccB != null) {
484                forwardIndexB = cccB.newIndex;
485            } else {
486                int tmp = tryGetTrunkPrefixOmittedIndex(b, 0);
487                if (tmp >= 0) {
488                    forwardIndexB = tmp;
489                    trunkPrefixIsOmittedB = true;
490                }
491            }
492        }
493
494        int backwardIndexA = a.length() - 1;
495        int backwardIndexB = b.length() - 1;
496        while (backwardIndexA >= forwardIndexA && backwardIndexB >= forwardIndexB) {
497            boolean skip_compare = false;
498            final char chA = a.charAt(backwardIndexA);
499            final char chB = b.charAt(backwardIndexB);
500            if (isSeparator(chA)) {
501                backwardIndexA--;
502                skip_compare = true;
503            }
504            if (isSeparator(chB)) {
505                backwardIndexB--;
506                skip_compare = true;
507            }
508
509            if (!skip_compare) {
510                if (chA != chB) {
511                    return false;
512                }
513                backwardIndexA--;
514                backwardIndexB--;
515            }
516        }
517
518        if (okToIgnorePrefix) {
519            if ((trunkPrefixIsOmittedA && forwardIndexA <= backwardIndexA) ||
520                !checkPrefixIsIgnorable(a, forwardIndexA, backwardIndexA)) {
521                if (acceptInvalidCCCPrefix) {
522                    // Maybe the code handling the special case for Thailand makes the
523                    // result garbled, so disable the code and try again.
524                    // e.g. "16610001234" must equal to "6610001234", but with
525                    //      Thailand-case handling code, they become equal to each other.
526                    //
527                    // Note: we select simplicity rather than adding some complicated
528                    //       logic here for performance(like "checking whether remaining
529                    //       numbers are just 66 or not"), assuming inputs are small
530                    //       enough.
531                    return compare(a, b, false);
532                } else {
533                    return false;
534                }
535            }
536            if ((trunkPrefixIsOmittedB && forwardIndexB <= backwardIndexB) ||
537                !checkPrefixIsIgnorable(b, forwardIndexA, backwardIndexB)) {
538                if (acceptInvalidCCCPrefix) {
539                    return compare(a, b, false);
540                } else {
541                    return false;
542                }
543            }
544        } else {
545            // In the US, 1-650-555-1234 must be equal to 650-555-1234,
546            // while 090-1234-1234 must not be equalt to 90-1234-1234 in Japan.
547            // This request exists just in US (with 1 trunk (NDD) prefix).
548            // In addition, "011 11 7005554141" must not equal to "+17005554141",
549            // while "011 1 7005554141" must equal to "+17005554141"
550            //
551            // In this comparison, we ignore the prefix '1' just once, when
552            // - at least either does not have CCC, or
553            // - the remaining non-separator number is 1
554            boolean maybeNamp = !bothHasCountryCallingCode;
555            while (backwardIndexA >= forwardIndexA) {
556                final char chA = a.charAt(backwardIndexA);
557                if (isDialable(chA)) {
558                    if (maybeNamp && tryGetISODigit(chA) == 1) {
559                        maybeNamp = false;
560                    } else {
561                        return false;
562                    }
563                }
564                backwardIndexA--;
565            }
566            while (backwardIndexB >= forwardIndexB) {
567                final char chB = b.charAt(backwardIndexB);
568                if (isDialable(chB)) {
569                    if (maybeNamp && tryGetISODigit(chB) == 1) {
570                        maybeNamp = false;
571                    } else {
572                        return false;
573                    }
574                }
575                backwardIndexB--;
576            }
577        }
578
579        return true;
580    }
581
582    /**
583     * Returns the rightmost MIN_MATCH (5) characters in the network portion
584     * in *reversed* order
585     *
586     * This can be used to do a database lookup against the column
587     * that stores getStrippedReversed()
588     *
589     * Returns null if phoneNumber == null
590     */
591    public static String
592    toCallerIDMinMatch(String phoneNumber) {
593        String np = extractNetworkPortion(phoneNumber);
594        return internalGetStrippedReversed(np, MIN_MATCH);
595    }
596
597    /**
598     * Returns the network portion reversed.
599     * This string is intended to go into an index column for a
600     * database lookup.
601     *
602     * Returns null if phoneNumber == null
603     */
604    public static String
605    getStrippedReversed(String phoneNumber) {
606        String np = extractNetworkPortion(phoneNumber);
607
608        if (np == null) return null;
609
610        return internalGetStrippedReversed(np, np.length());
611    }
612
613    /**
614     * Returns the last numDigits of the reversed phone number
615     * Returns null if np == null
616     */
617    private static String
618    internalGetStrippedReversed(String np, int numDigits) {
619        if (np == null) return null;
620
621        StringBuilder ret = new StringBuilder(numDigits);
622        int length = np.length();
623
624        for (int i = length - 1, s = length
625            ; i >= 0 && (s - i) <= numDigits ; i--
626        ) {
627            char c = np.charAt(i);
628
629            ret.append(c);
630        }
631
632        return ret.toString();
633    }
634
635    /**
636     * Basically: makes sure there's a + in front of a
637     * TOA_International number
638     *
639     * Returns null if s == null
640     */
641    public static String
642    stringFromStringAndTOA(String s, int TOA) {
643        if (s == null) return null;
644
645        if (TOA == TOA_International && s.length() > 0 && s.charAt(0) != '+') {
646            return "+" + s;
647        }
648
649        return s;
650    }
651
652    /**
653     * Returns the TOA for the given dial string
654     * Basically, returns TOA_International if there's a + prefix
655     */
656
657    public static int
658    toaFromString(String s) {
659        if (s != null && s.length() > 0 && s.charAt(0) == '+') {
660            return TOA_International;
661        }
662
663        return TOA_Unknown;
664    }
665
666    /**
667     *  3GPP TS 24.008 10.5.4.7
668     *  Called Party BCD Number
669     *
670     *  See Also TS 51.011 10.5.1 "dialing number/ssc string"
671     *  and TS 11.11 "10.3.1 EF adn (Abbreviated dialing numbers)"
672     *
673     * @param bytes the data buffer
674     * @param offset should point to the TOA (aka. TON/NPI) octet after the length byte
675     * @param length is the number of bytes including TOA byte
676     *                and must be at least 2
677     *
678     * @return partial string on invalid decode
679     *
680     * FIXME(mkf) support alphanumeric address type
681     *  currently implemented in SMSMessage.getAddress()
682     */
683    public static String
684    calledPartyBCDToString (byte[] bytes, int offset, int length) {
685        boolean prependPlus = false;
686        StringBuilder ret = new StringBuilder(1 + length * 2);
687
688        if (length < 2) {
689            return "";
690        }
691
692        if ((bytes[offset] & 0xff) == TOA_International) {
693            prependPlus = true;
694        }
695
696        internalCalledPartyBCDFragmentToString(
697                ret, bytes, offset + 1, length - 1);
698
699        if (prependPlus && ret.length() == 0) {
700            // If the only thing there is a prepended plus, return ""
701            return "";
702        }
703
704        if (prependPlus) {
705            // This is an "international number" and should have
706            // a plus prepended to the dialing number. But there
707            // can also be Gsm MMI codes as defined in TS 22.030 6.5.2
708            // so we need to handle those also.
709            //
710            // http://web.telia.com/~u47904776/gsmkode.htm is a
711            // has a nice list of some of these GSM codes.
712            //
713            // Examples are:
714            //   **21*+886988171479#
715            //   **21*8311234567#
716            //   *21#
717            //   #21#
718            //   *#21#
719            //   *31#+11234567890
720            //   #31#+18311234567
721            //   #31#8311234567
722            //   18311234567
723            //   +18311234567#
724            //   +18311234567
725            // Odd ball cases that some phones handled
726            // where there is no dialing number so they
727            // append the "+"
728            //   *21#+
729            //   **21#+
730            String retString = ret.toString();
731            Pattern p = Pattern.compile("(^[#*])(.*)([#*])(.*)(#)$");
732            Matcher m = p.matcher(retString);
733            if (m.matches()) {
734                if ("".equals(m.group(2))) {
735                    // Started with two [#*] ends with #
736                    // So no dialing number and we'll just
737                    // append a +, this handles **21#+
738                    ret = new StringBuilder();
739                    ret.append(m.group(1));
740                    ret.append(m.group(3));
741                    ret.append(m.group(4));
742                    ret.append(m.group(5));
743                    ret.append("+");
744                } else {
745                    // Starts with [#*] and ends with #
746                    // Assume group 4 is a dialing number
747                    // such as *21*+1234554#
748                    ret = new StringBuilder();
749                    ret.append(m.group(1));
750                    ret.append(m.group(2));
751                    ret.append(m.group(3));
752                    ret.append("+");
753                    ret.append(m.group(4));
754                    ret.append(m.group(5));
755                }
756            } else {
757                p = Pattern.compile("(^[#*])(.*)([#*])(.*)");
758                m = p.matcher(retString);
759                if (m.matches()) {
760                    // Starts with [#*] and only one other [#*]
761                    // Assume the data after last [#*] is dialing
762                    // number (i.e. group 4) such as *31#+11234567890.
763                    // This also includes the odd ball *21#+
764                    ret = new StringBuilder();
765                    ret.append(m.group(1));
766                    ret.append(m.group(2));
767                    ret.append(m.group(3));
768                    ret.append("+");
769                    ret.append(m.group(4));
770                } else {
771                    // Does NOT start with [#*] just prepend '+'
772                    ret = new StringBuilder();
773                    ret.append('+');
774                    ret.append(retString);
775                }
776            }
777        }
778
779        return ret.toString();
780    }
781
782    private static void
783    internalCalledPartyBCDFragmentToString(
784        StringBuilder sb, byte [] bytes, int offset, int length) {
785        for (int i = offset ; i < length + offset ; i++) {
786            byte b;
787            char c;
788
789            c = bcdToChar((byte)(bytes[i] & 0xf));
790
791            if (c == 0) {
792                return;
793            }
794            sb.append(c);
795
796            // FIXME(mkf) TS 23.040 9.1.2.3 says
797            // "if a mobile receives 1111 in a position prior to
798            // the last semi-octet then processing shall commense with
799            // the next semi-octet and the intervening
800            // semi-octet shall be ignored"
801            // How does this jive with 24,008 10.5.4.7
802
803            b = (byte)((bytes[i] >> 4) & 0xf);
804
805            if (b == 0xf && i + 1 == length + offset) {
806                //ignore final 0xf
807                break;
808            }
809
810            c = bcdToChar(b);
811            if (c == 0) {
812                return;
813            }
814
815            sb.append(c);
816        }
817
818    }
819
820    /**
821     * Like calledPartyBCDToString, but field does not start with a
822     * TOA byte. For example: SIM ADN extension fields
823     */
824
825    public static String
826    calledPartyBCDFragmentToString(byte [] bytes, int offset, int length) {
827        StringBuilder ret = new StringBuilder(length * 2);
828
829        internalCalledPartyBCDFragmentToString(ret, bytes, offset, length);
830
831        return ret.toString();
832    }
833
834    /** returns 0 on invalid value */
835    private static char
836    bcdToChar(byte b) {
837        if (b < 0xa) {
838            return (char)('0' + b);
839        } else switch (b) {
840            case 0xa: return '*';
841            case 0xb: return '#';
842            case 0xc: return PAUSE;
843            case 0xd: return WILD;
844
845            default: return 0;
846        }
847    }
848
849    private static int
850    charToBCD(char c) {
851        if (c >= '0' && c <= '9') {
852            return c - '0';
853        } else if (c == '*') {
854            return 0xa;
855        } else if (c == '#') {
856            return 0xb;
857        } else if (c == PAUSE) {
858            return 0xc;
859        } else if (c == WILD) {
860            return 0xd;
861        } else {
862            throw new RuntimeException ("invalid char for BCD " + c);
863        }
864    }
865
866    /**
867     * Return true iff the network portion of <code>address</code> is,
868     * as far as we can tell on the device, suitable for use as an SMS
869     * destination address.
870     */
871    public static boolean isWellFormedSmsAddress(String address) {
872        String networkPortion =
873                PhoneNumberUtils.extractNetworkPortion(address);
874
875        return (!(networkPortion.equals("+")
876                  || TextUtils.isEmpty(networkPortion)))
877               && isDialable(networkPortion);
878    }
879
880    public static boolean isGlobalPhoneNumber(String phoneNumber) {
881        if (TextUtils.isEmpty(phoneNumber)) {
882            return false;
883        }
884
885        Matcher match = GLOBAL_PHONE_NUMBER_PATTERN.matcher(phoneNumber);
886        return match.matches();
887    }
888
889    private static boolean isDialable(String address) {
890        for (int i = 0, count = address.length(); i < count; i++) {
891            if (!isDialable(address.charAt(i))) {
892                return false;
893            }
894        }
895        return true;
896    }
897
898    private static boolean isNonSeparator(String address) {
899        for (int i = 0, count = address.length(); i < count; i++) {
900            if (!isNonSeparator(address.charAt(i))) {
901                return false;
902            }
903        }
904        return true;
905    }
906    /**
907     * Note: calls extractNetworkPortion(), so do not use for
908     * SIM EF[ADN] style records
909     *
910     * Returns null if network portion is empty.
911     */
912    public static byte[]
913    networkPortionToCalledPartyBCD(String s) {
914        String networkPortion = extractNetworkPortion(s);
915        return numberToCalledPartyBCDHelper(networkPortion, false);
916    }
917
918    /**
919     * Same as {@link #networkPortionToCalledPartyBCD}, but includes a
920     * one-byte length prefix.
921     */
922    public static byte[]
923    networkPortionToCalledPartyBCDWithLength(String s) {
924        String networkPortion = extractNetworkPortion(s);
925        return numberToCalledPartyBCDHelper(networkPortion, true);
926    }
927
928    /**
929     * Convert a dialing number to BCD byte array
930     *
931     * @param number dialing number string
932     *        if the dialing number starts with '+', set to internationl TOA
933     * @return BCD byte array
934     */
935    public static byte[]
936    numberToCalledPartyBCD(String number) {
937        return numberToCalledPartyBCDHelper(number, false);
938    }
939
940    /**
941     * If includeLength is true, prepend a one-byte length value to
942     * the return array.
943     */
944    private static byte[]
945    numberToCalledPartyBCDHelper(String number, boolean includeLength) {
946        int numberLenReal = number.length();
947        int numberLenEffective = numberLenReal;
948        boolean hasPlus = number.indexOf('+') != -1;
949        if (hasPlus) numberLenEffective--;
950
951        if (numberLenEffective == 0) return null;
952
953        int resultLen = (numberLenEffective + 1) / 2;  // Encoded numbers require only 4 bits each.
954        int extraBytes = 1;                            // Prepended TOA byte.
955        if (includeLength) extraBytes++;               // Optional prepended length byte.
956        resultLen += extraBytes;
957
958        byte[] result = new byte[resultLen];
959
960        int digitCount = 0;
961        for (int i = 0; i < numberLenReal; i++) {
962            char c = number.charAt(i);
963            if (c == '+') continue;
964            int shift = ((digitCount & 0x01) == 1) ? 4 : 0;
965            result[extraBytes + (digitCount >> 1)] |= (byte)((charToBCD(c) & 0x0F) << shift);
966            digitCount++;
967        }
968
969        // 1-fill any trailing odd nibble/quartet.
970        if ((digitCount & 0x01) == 1) result[extraBytes + (digitCount >> 1)] |= 0xF0;
971
972        int offset = 0;
973        if (includeLength) result[offset++] = (byte)(resultLen - 1);
974        result[offset] = (byte)(hasPlus ? TOA_International : TOA_Unknown);
975
976        return result;
977    }
978
979    //================ Number formatting =========================
980
981    /** The current locale is unknown, look for a country code or don't format */
982    public static final int FORMAT_UNKNOWN = 0;
983    /** NANP formatting */
984    public static final int FORMAT_NANP = 1;
985    /** Japanese formatting */
986    public static final int FORMAT_JAPAN = 2;
987
988    /** List of country codes for countries that use the NANP */
989    private static final String[] NANP_COUNTRIES = new String[] {
990        "US", // United States
991        "CA", // Canada
992        "AS", // American Samoa
993        "AI", // Anguilla
994        "AG", // Antigua and Barbuda
995        "BS", // Bahamas
996        "BB", // Barbados
997        "BM", // Bermuda
998        "VG", // British Virgin Islands
999        "KY", // Cayman Islands
1000        "DM", // Dominica
1001        "DO", // Dominican Republic
1002        "GD", // Grenada
1003        "GU", // Guam
1004        "JM", // Jamaica
1005        "PR", // Puerto Rico
1006        "MS", // Montserrat
1007        "MP", // Northern Mariana Islands
1008        "KN", // Saint Kitts and Nevis
1009        "LC", // Saint Lucia
1010        "VC", // Saint Vincent and the Grenadines
1011        "TT", // Trinidad and Tobago
1012        "TC", // Turks and Caicos Islands
1013        "VI", // U.S. Virgin Islands
1014    };
1015
1016    /**
1017     * Breaks the given number down and formats it according to the rules
1018     * for the country the number is from.
1019     *
1020     * @param source the phone number to format
1021     * @return a locally acceptable formatting of the input, or the raw input if
1022     *  formatting rules aren't known for the number
1023     */
1024    public static String formatNumber(String source) {
1025        SpannableStringBuilder text = new SpannableStringBuilder(source);
1026        formatNumber(text, getFormatTypeForLocale(Locale.getDefault()));
1027        return text.toString();
1028    }
1029
1030    /**
1031     * Returns the phone number formatting type for the given locale.
1032     *
1033     * @param locale The locale of interest, usually {@link Locale#getDefault()}
1034     * @return the formatting type for the given locale, or FORMAT_UNKNOWN if the formatting
1035     * rules are not known for the given locale
1036     */
1037    public static int getFormatTypeForLocale(Locale locale) {
1038        String country = locale.getCountry();
1039
1040        return getFormatTypeFromCountryCode(country);
1041    }
1042
1043    /**
1044     * Formats a phone number in-place. Currently only supports NANP formatting.
1045     *
1046     * @param text The number to be formatted, will be modified with the formatting
1047     * @param defaultFormattingType The default formatting rules to apply if the number does
1048     * not begin with +<country_code>
1049     */
1050    public static void formatNumber(Editable text, int defaultFormattingType) {
1051        int formatType = defaultFormattingType;
1052
1053        if (text.length() > 2 && text.charAt(0) == '+') {
1054            if (text.charAt(1) == '1') {
1055                formatType = FORMAT_NANP;
1056            } else if (text.length() >= 3 && text.charAt(1) == '8'
1057                && text.charAt(2) == '1') {
1058                formatType = FORMAT_JAPAN;
1059            } else {
1060                return;
1061            }
1062        }
1063
1064        switch (formatType) {
1065            case FORMAT_NANP:
1066                formatNanpNumber(text);
1067                return;
1068            case FORMAT_JAPAN:
1069                formatJapaneseNumber(text);
1070                return;
1071        }
1072    }
1073
1074    private static final int NANP_STATE_DIGIT = 1;
1075    private static final int NANP_STATE_PLUS = 2;
1076    private static final int NANP_STATE_ONE = 3;
1077    private static final int NANP_STATE_DASH = 4;
1078
1079    /**
1080     * Formats a phone number in-place using the NANP formatting rules. Numbers will be formatted
1081     * as:
1082     *
1083     * <p><code>
1084     * xxxxx
1085     * xxx-xxxx
1086     * xxx-xxx-xxxx
1087     * 1-xxx-xxx-xxxx
1088     * +1-xxx-xxx-xxxx
1089     * </code></p>
1090     *
1091     * @param text the number to be formatted, will be modified with the formatting
1092     */
1093    public static void formatNanpNumber(Editable text) {
1094        int length = text.length();
1095        if (length > "+1-nnn-nnn-nnnn".length()) {
1096            // The string is too long to be formatted
1097            return;
1098        } else if (length <= 5) {
1099            // The string is either a shortcode or too short to be formatted
1100            return;
1101        }
1102
1103        CharSequence saved = text.subSequence(0, length);
1104
1105        // Strip the dashes first, as we're going to add them back
1106        int p = 0;
1107        while (p < text.length()) {
1108            if (text.charAt(p) == '-') {
1109                text.delete(p, p + 1);
1110            } else {
1111                p++;
1112            }
1113        }
1114        length = text.length();
1115
1116        // When scanning the number we record where dashes need to be added,
1117        // if they're non-0 at the end of the scan the dashes will be added in
1118        // the proper places.
1119        int dashPositions[] = new int[3];
1120        int numDashes = 0;
1121
1122        int state = NANP_STATE_DIGIT;
1123        int numDigits = 0;
1124        for (int i = 0; i < length; i++) {
1125            char c = text.charAt(i);
1126            switch (c) {
1127                case '1':
1128                    if (numDigits == 0 || state == NANP_STATE_PLUS) {
1129                        state = NANP_STATE_ONE;
1130                        break;
1131                    }
1132                    // fall through
1133                case '2':
1134                case '3':
1135                case '4':
1136                case '5':
1137                case '6':
1138                case '7':
1139                case '8':
1140                case '9':
1141                case '0':
1142                    if (state == NANP_STATE_PLUS) {
1143                        // Only NANP number supported for now
1144                        text.replace(0, length, saved);
1145                        return;
1146                    } else if (state == NANP_STATE_ONE) {
1147                        // Found either +1 or 1, follow it up with a dash
1148                        dashPositions[numDashes++] = i;
1149                    } else if (state != NANP_STATE_DASH && (numDigits == 3 || numDigits == 6)) {
1150                        // Found a digit that should be after a dash that isn't
1151                        dashPositions[numDashes++] = i;
1152                    }
1153                    state = NANP_STATE_DIGIT;
1154                    numDigits++;
1155                    break;
1156
1157                case '-':
1158                    state = NANP_STATE_DASH;
1159                    break;
1160
1161                case '+':
1162                    if (i == 0) {
1163                        // Plus is only allowed as the first character
1164                        state = NANP_STATE_PLUS;
1165                        break;
1166                    }
1167                    // Fall through
1168                default:
1169                    // Unknown character, bail on formatting
1170                    text.replace(0, length, saved);
1171                    return;
1172            }
1173        }
1174
1175        if (numDigits == 7) {
1176            // With 7 digits we want xxx-xxxx, not xxx-xxx-x
1177            numDashes--;
1178        }
1179
1180        // Actually put the dashes in place
1181        for (int i = 0; i < numDashes; i++) {
1182            int pos = dashPositions[i];
1183            text.replace(pos + i, pos + i, "-");
1184        }
1185
1186        // Remove trailing dashes
1187        int len = text.length();
1188        while (len > 0) {
1189            if (text.charAt(len - 1) == '-') {
1190                text.delete(len - 1, len);
1191                len--;
1192            } else {
1193                break;
1194            }
1195        }
1196    }
1197
1198    /**
1199     * Formats a phone number in-place using the Japanese formatting rules.
1200     * Numbers will be formatted as:
1201     *
1202     * <p><code>
1203     * 03-xxxx-xxxx
1204     * 090-xxxx-xxxx
1205     * 0120-xxx-xxx
1206     * +81-3-xxxx-xxxx
1207     * +81-90-xxxx-xxxx
1208     * </code></p>
1209     *
1210     * @param text the number to be formatted, will be modified with
1211     * the formatting
1212     */
1213    public static void formatJapaneseNumber(Editable text) {
1214        JapanesePhoneNumberFormatter.format(text);
1215    }
1216
1217    // Three and four digit phone numbers for either special services,
1218    // or 3-6 digit addresses from the network (eg carrier-originated SMS messages) should
1219    // not match.
1220    //
1221    // This constant used to be 5, but SMS short codes has increased in length and
1222    // can be easily 6 digits now days. Most countries have SMS short code length between
1223    // 3 to 6 digits. The exceptions are
1224    //
1225    // Australia: Short codes are six or eight digits in length, starting with the prefix "19"
1226    //            followed by an additional four or six digits and two.
1227    // Czech Republic: Codes are seven digits in length for MO and five (not billed) or
1228    //            eight (billed) for MT direction
1229    //
1230    // see http://en.wikipedia.org/wiki/Short_code#Regional_differences for reference
1231    //
1232    // However, in order to loose match 650-555-1212 and 555-1212, we need to set the min match
1233    // to 7.
1234    static final int MIN_MATCH = 7;
1235
1236    /**
1237     * isEmergencyNumber: checks a given number against the list of
1238     *   emergency numbers provided by the RIL and SIM card.
1239     *
1240     * @param number the number to look up.
1241     * @return if the number is in the list of emergency numbers
1242     * listed in the ril / sim, then return true, otherwise false.
1243     */
1244    public static boolean isEmergencyNumber(String number) {
1245        // If the number passed in is null, just return false:
1246        if (number == null) return false;
1247
1248        // Strip the separators from the number before comparing it
1249        // to the list.
1250        number = extractNetworkPortion(number);
1251
1252        // retrieve the list of emergency numbers
1253        String numbers = SystemProperties.get("ro.ril.ecclist");
1254
1255        if (!TextUtils.isEmpty(numbers)) {
1256            // searches through the comma-separated list for a match,
1257            // return true if one is found.
1258            for (String emergencyNum : numbers.split(",")) {
1259                if (emergencyNum.equals(number)) {
1260                    return true;
1261                }
1262            }
1263            // no matches found against the list!
1264            return false;
1265        }
1266
1267        //no ecclist system property, so use our own list.
1268        return (number.equals("112") || number.equals("911"));
1269    }
1270
1271    /**
1272     * isVoiceMailNumber: checks a given number against the voicemail
1273     *   number provided by the RIL and SIM card. The caller must have
1274     *   the READ_PHONE_STATE credential.
1275     *
1276     * @param number the number to look up.
1277     * @return true if the number is in the list of voicemail. False
1278     * otherwise, including if the caller does not have the permission
1279     * to read the VM number.
1280     * @hide TODO: pending API Council approval
1281     */
1282    public static boolean isVoiceMailNumber(String number) {
1283        String vmNumber;
1284
1285        try {
1286            vmNumber = TelephonyManager.getDefault().getVoiceMailNumber();
1287        } catch (SecurityException ex) {
1288            return false;
1289        }
1290
1291        // Strip the separators from the number before comparing it
1292        // to the list.
1293        number = extractNetworkPortion(number);
1294
1295        // compare tolerates null so we need to make sure that we
1296        // don't return true when both are null.
1297        return !TextUtils.isEmpty(number) && compare(number, vmNumber);
1298    }
1299
1300    /**
1301     * Translates any alphabetic letters (i.e. [A-Za-z]) in the
1302     * specified phone number into the equivalent numeric digits,
1303     * according to the phone keypad letter mapping described in
1304     * ITU E.161 and ISO/IEC 9995-8.
1305     *
1306     * @return the input string, with alpha letters converted to numeric
1307     *         digits using the phone keypad letter mapping.  For example,
1308     *         an input of "1-800-GOOG-411" will return "1-800-4664-411".
1309     */
1310    public static String convertKeypadLettersToDigits(String input) {
1311        if (input == null) {
1312            return input;
1313        }
1314        int len = input.length();
1315        if (len == 0) {
1316            return input;
1317        }
1318
1319        char[] out = input.toCharArray();
1320
1321        for (int i = 0; i < len; i++) {
1322            char c = out[i];
1323            // If this char isn't in KEYPAD_MAP at all, just leave it alone.
1324            out[i] = (char) KEYPAD_MAP.get(c, c);
1325        }
1326
1327        return new String(out);
1328    }
1329
1330    /**
1331     * The phone keypad letter mapping (see ITU E.161 or ISO/IEC 9995-8.)
1332     * TODO: This should come from a resource.
1333     */
1334    private static final SparseIntArray KEYPAD_MAP = new SparseIntArray();
1335    static {
1336        KEYPAD_MAP.put('a', '2'); KEYPAD_MAP.put('b', '2'); KEYPAD_MAP.put('c', '2');
1337        KEYPAD_MAP.put('A', '2'); KEYPAD_MAP.put('B', '2'); KEYPAD_MAP.put('C', '2');
1338
1339        KEYPAD_MAP.put('d', '3'); KEYPAD_MAP.put('e', '3'); KEYPAD_MAP.put('f', '3');
1340        KEYPAD_MAP.put('D', '3'); KEYPAD_MAP.put('E', '3'); KEYPAD_MAP.put('F', '3');
1341
1342        KEYPAD_MAP.put('g', '4'); KEYPAD_MAP.put('h', '4'); KEYPAD_MAP.put('i', '4');
1343        KEYPAD_MAP.put('G', '4'); KEYPAD_MAP.put('H', '4'); KEYPAD_MAP.put('I', '4');
1344
1345        KEYPAD_MAP.put('j', '5'); KEYPAD_MAP.put('k', '5'); KEYPAD_MAP.put('l', '5');
1346        KEYPAD_MAP.put('J', '5'); KEYPAD_MAP.put('K', '5'); KEYPAD_MAP.put('L', '5');
1347
1348        KEYPAD_MAP.put('m', '6'); KEYPAD_MAP.put('n', '6'); KEYPAD_MAP.put('o', '6');
1349        KEYPAD_MAP.put('M', '6'); KEYPAD_MAP.put('N', '6'); KEYPAD_MAP.put('O', '6');
1350
1351        KEYPAD_MAP.put('p', '7'); KEYPAD_MAP.put('q', '7'); KEYPAD_MAP.put('r', '7'); KEYPAD_MAP.put('s', '7');
1352        KEYPAD_MAP.put('P', '7'); KEYPAD_MAP.put('Q', '7'); KEYPAD_MAP.put('R', '7'); KEYPAD_MAP.put('S', '7');
1353
1354        KEYPAD_MAP.put('t', '8'); KEYPAD_MAP.put('u', '8'); KEYPAD_MAP.put('v', '8');
1355        KEYPAD_MAP.put('T', '8'); KEYPAD_MAP.put('U', '8'); KEYPAD_MAP.put('V', '8');
1356
1357        KEYPAD_MAP.put('w', '9'); KEYPAD_MAP.put('x', '9'); KEYPAD_MAP.put('y', '9'); KEYPAD_MAP.put('z', '9');
1358        KEYPAD_MAP.put('W', '9'); KEYPAD_MAP.put('X', '9'); KEYPAD_MAP.put('Y', '9'); KEYPAD_MAP.put('Z', '9');
1359    }
1360
1361    //================ Plus Code formatting =========================
1362    private static final char PLUS_SIGN_CHAR = '+';
1363    private static final String PLUS_SIGN_STRING = "+";
1364    private static final String NANP_IDP_STRING = "011";
1365    private static final int NANP_LENGTH = 10;
1366
1367    /**
1368     * This function checks if there is a plus sign (+) in the passed-in dialing number.
1369     * If there is, it processes the plus sign based on the default telephone
1370     * numbering plan of the system when the phone is activated and the current
1371     * telephone numbering plan of the system that the phone is camped on.
1372     * Currently, we only support the case that the default and current telephone
1373     * numbering plans are North American Numbering Plan(NANP).
1374     *
1375     * The passed-in dialStr should only contain the valid format as described below,
1376     * 1) the 1st character in the dialStr should be one of the really dialable
1377     *    characters listed below
1378     *    ISO-LATIN characters 0-9, *, # , +
1379     * 2) the dialStr should already strip out the separator characters,
1380     *    every character in the dialStr should be one of the non separator characters
1381     *    listed below
1382     *    ISO-LATIN characters 0-9, *, # , +, WILD, WAIT, PAUSE
1383     *
1384     * Otherwise, this function returns the dial string passed in
1385     *
1386     * @param dialStr the original dial string
1387     * @return the converted dial string if the current/default countries belong to NANP,
1388     * and if there is the "+" in the original dial string. Otherwise, the original dial
1389     * string returns.
1390     *
1391     * This API is for CDMA only
1392     *
1393     * @hide TODO: pending API Council approval
1394     */
1395    public static String cdmaCheckAndProcessPlusCode(String dialStr) {
1396        if (!TextUtils.isEmpty(dialStr)) {
1397            if (isReallyDialable(dialStr.charAt(0)) &&
1398                isNonSeparator(dialStr)) {
1399                String currIso = SystemProperties.get(PROPERTY_OPERATOR_ISO_COUNTRY, "");
1400                String defaultIso = SystemProperties.get(PROPERTY_ICC_OPERATOR_ISO_COUNTRY, "");
1401                if (!TextUtils.isEmpty(currIso) && !TextUtils.isEmpty(defaultIso)) {
1402                    return cdmaCheckAndProcessPlusCodeByNumberFormat(dialStr,
1403                            getFormatTypeFromCountryCode(currIso),
1404                            getFormatTypeFromCountryCode(defaultIso));
1405                }
1406            }
1407        }
1408        return dialStr;
1409    }
1410
1411    /**
1412     * This function should be called from checkAndProcessPlusCode only
1413     * And it is used for test purpose also.
1414     *
1415     * It checks the dial string by looping through the network portion,
1416     * post dial portion 1, post dial porting 2, etc. If there is any
1417     * plus sign, then process the plus sign.
1418     * Currently, this function supports the plus sign conversion within NANP only.
1419     * Specifically, it handles the plus sign in the following ways:
1420     * 1)+1NANP,remove +, e.g.
1421     *   +18475797000 is converted to 18475797000,
1422     * 2)+NANP or +non-NANP Numbers,replace + with the current NANP IDP, e.g,
1423     *   +8475797000 is converted to 0118475797000,
1424     *   +11875767800 is converted to 01111875767800
1425     * 3)+1NANP in post dial string(s), e.g.
1426     *   8475797000;+18475231753 is converted to 8475797000;18475231753
1427     *
1428     *
1429     * @param dialStr the original dial string
1430     * @param currFormat the numbering system of the current country that the phone is camped on
1431     * @param defaultFormat the numbering system of the country that the phone is activated on
1432     * @return the converted dial string if the current/default countries belong to NANP,
1433     * and if there is the "+" in the original dial string. Otherwise, the original dial
1434     * string returns.
1435     *
1436     * @hide
1437     */
1438    public static String
1439    cdmaCheckAndProcessPlusCodeByNumberFormat(String dialStr,int currFormat,int defaultFormt) {
1440        String retStr = dialStr;
1441
1442        // Checks if the plus sign character is in the passed-in dial string
1443        if (dialStr != null &&
1444            dialStr.lastIndexOf(PLUS_SIGN_STRING) != -1) {
1445            // Format the string based on the rules for the country the number is from,
1446            // and the current country the phone is camped on.
1447            if ((currFormat == defaultFormt) && (currFormat == FORMAT_NANP)) {
1448                // Handle case where default and current telephone numbering plans are NANP.
1449                String postDialStr = null;
1450                String tempDialStr = dialStr;
1451
1452                // Sets the retStr to null since the conversion will be performed below.
1453                retStr = null;
1454                if (DBG) log("checkAndProcessPlusCode,dialStr=" + dialStr);
1455                // This routine is to process the plus sign in the dial string by loop through
1456                // the network portion, post dial portion 1, post dial portion 2... etc. if
1457                // applied
1458                do {
1459                    String networkDialStr;
1460                    networkDialStr = extractNetworkPortion(tempDialStr);
1461                    // Handles the conversion within NANP
1462                    networkDialStr = processPlusCodeWithinNanp(networkDialStr);
1463
1464                    // Concatenates the string that is converted from network portion
1465                    if (!TextUtils.isEmpty(networkDialStr)) {
1466                        if (retStr == null) {
1467                            retStr = networkDialStr;
1468                        } else {
1469                            retStr = retStr.concat(networkDialStr);
1470                        }
1471                    } else {
1472                        // This should never happen since we checked the if dialStr is null
1473                        // and if it contains the plus sign in the beginning of this function.
1474                        // The plus sign is part of the network portion.
1475                        Log.e("checkAndProcessPlusCode: null newDialStr", networkDialStr);
1476                        return dialStr;
1477                    }
1478                    postDialStr = extractPostDialPortion(tempDialStr);
1479                    if (!TextUtils.isEmpty(postDialStr)) {
1480                        int dialableIndex = findDialableIndexFromPostDialStr(postDialStr);
1481
1482                        // dialableIndex should always be greater than 0
1483                        if (dialableIndex >= 1) {
1484                            retStr = appendPwCharBackToOrigDialStr(dialableIndex,
1485                                     retStr,postDialStr);
1486                            // Skips the P/W character, extracts the dialable portion
1487                            tempDialStr = postDialStr.substring(dialableIndex);
1488                        } else {
1489                            // Non-dialable character such as P/W should not be at the end of
1490                            // the dial string after P/W processing in CdmaConnection.java
1491                            // Set the postDialStr to "" to break out of the loop
1492                            if (dialableIndex < 0) {
1493                                postDialStr = "";
1494                            }
1495                            Log.e("wrong postDialStr=", postDialStr);
1496                        }
1497                    }
1498                    if (DBG) log("checkAndProcessPlusCode,postDialStr=" + postDialStr);
1499                } while (!TextUtils.isEmpty(postDialStr) && !TextUtils.isEmpty(tempDialStr));
1500            } else {
1501                // TODO: Support NANP international conversion and other telephone numbering plans.
1502                // Currently the phone is never used in non-NANP system, so return the original
1503                // dial string.
1504                Log.e("checkAndProcessPlusCode:non-NANP not supported", dialStr);
1505            }
1506        }
1507        return retStr;
1508     }
1509
1510    // This function gets the default international dialing prefix
1511    private static String getDefaultIdp( ) {
1512        String ps = null;
1513        SystemProperties.get(PROPERTY_IDP_STRING, ps);
1514        if (TextUtils.isEmpty(ps)) {
1515            ps = NANP_IDP_STRING;
1516        }
1517        return ps;
1518    }
1519
1520    private static boolean isTwoToNine (char c) {
1521        if (c >= '2' && c <= '9') {
1522            return true;
1523        } else {
1524            return false;
1525        }
1526    }
1527
1528    private static int getFormatTypeFromCountryCode (String country) {
1529        // Check for the NANP countries
1530        int length = NANP_COUNTRIES.length;
1531        for (int i = 0; i < length; i++) {
1532            if (NANP_COUNTRIES[i].compareToIgnoreCase(country) == 0) {
1533                return FORMAT_NANP;
1534            }
1535        }
1536        if ("jp".compareToIgnoreCase(country) == 0) {
1537            return FORMAT_JAPAN;
1538        }
1539        return FORMAT_UNKNOWN;
1540    }
1541
1542    /**
1543     * This function checks if the passed in string conforms to the NANP format
1544     * i.e. NXX-NXX-XXXX, N is any digit 2-9 and X is any digit 0-9
1545     */
1546    private static boolean isNanp (String dialStr) {
1547        boolean retVal = false;
1548        if (dialStr != null) {
1549            if (dialStr.length() == NANP_LENGTH) {
1550                if (isTwoToNine(dialStr.charAt(0)) &&
1551                    isTwoToNine(dialStr.charAt(3))) {
1552                    retVal = true;
1553                    for (int i=1; i<NANP_LENGTH; i++ ) {
1554                        char c=dialStr.charAt(i);
1555                        if (!PhoneNumberUtils.isISODigit(c)) {
1556                            retVal = false;
1557                            break;
1558                        }
1559                    }
1560                }
1561            }
1562        } else {
1563            Log.e("isNanp: null dialStr passed in", dialStr);
1564        }
1565        return retVal;
1566    }
1567
1568   /**
1569    * This function checks if the passed in string conforms to 1-NANP format
1570    */
1571    private static boolean isOneNanp(String dialStr) {
1572        boolean retVal = false;
1573        if (dialStr != null) {
1574            String newDialStr = dialStr.substring(1);
1575            if ((dialStr.charAt(0) == '1') && isNanp(newDialStr)) {
1576                retVal = true;
1577            }
1578        } else {
1579            Log.e("isOneNanp: null dialStr passed in", dialStr);
1580        }
1581        return retVal;
1582    }
1583
1584    /**
1585     * This function handles the plus code conversion within NANP CDMA network
1586     * If the number format is
1587     * 1)+1NANP,remove +,
1588     * 2)other than +1NANP, any + numbers,replace + with the current IDP
1589     */
1590    private static String processPlusCodeWithinNanp(String networkDialStr) {
1591        String retStr = networkDialStr;
1592
1593        if (DBG) log("processPlusCodeWithinNanp,networkDialStr=" + networkDialStr);
1594        // If there is a plus sign at the beginning of the dial string,
1595        // Convert the plus sign to the default IDP since it's an international number
1596        if (networkDialStr != null &
1597            networkDialStr.charAt(0) == PLUS_SIGN_CHAR &&
1598            networkDialStr.length() > 1) {
1599            String newStr = networkDialStr.substring(1);
1600            if (isOneNanp(newStr)) {
1601                // Remove the leading plus sign
1602                retStr = newStr;
1603             } else {
1604                 String idpStr = getDefaultIdp();
1605                 // Replaces the plus sign with the default IDP
1606                 retStr = networkDialStr.replaceFirst("[+]", idpStr);
1607            }
1608        }
1609        if (DBG) log("processPlusCodeWithinNanp,retStr=" + retStr);
1610        return retStr;
1611    }
1612
1613    // This function finds the index of the dialable character(s)
1614    // in the post dial string
1615    private static int findDialableIndexFromPostDialStr(String postDialStr) {
1616        for (int index = 0;index < postDialStr.length();index++) {
1617             char c = postDialStr.charAt(index);
1618             if (isReallyDialable(c)) {
1619                return index;
1620             }
1621        }
1622        return -1;
1623    }
1624
1625    // This function appends the non-diablable P/W character to the original
1626    // dial string based on the dialable index passed in
1627    private static String
1628    appendPwCharBackToOrigDialStr(int dialableIndex,String origStr, String dialStr) {
1629        String retStr;
1630
1631        // There is only 1 P/W character before the dialable characters
1632        if (dialableIndex == 1) {
1633            StringBuilder ret = new StringBuilder(origStr);
1634            ret = ret.append(dialStr.charAt(0));
1635            retStr = ret.toString();
1636        } else {
1637            // It means more than 1 P/W characters in the post dial string,
1638            // appends to retStr
1639            String nonDigitStr = dialStr.substring(0,dialableIndex);
1640            retStr = origStr.concat(nonDigitStr);
1641        }
1642        return retStr;
1643    }
1644
1645    //===== Begining of utility methods used in compareLoosely() =====
1646
1647    /**
1648     * Phone numbers are stored in "lookup" form in the database
1649     * as reversed strings to allow for caller ID lookup
1650     *
1651     * This method takes a phone number and makes a valid SQL "LIKE"
1652     * string that will match the lookup form
1653     *
1654     */
1655    /** all of a up to len must be an international prefix or
1656     *  separators/non-dialing digits
1657     */
1658    private static boolean
1659    matchIntlPrefix(String a, int len) {
1660        /* '([^0-9*#+pwn]\+[^0-9*#+pwn] | [^0-9*#+pwn]0(0|11)[^0-9*#+pwn] )$' */
1661        /*        0       1                           2 3 45               */
1662
1663        int state = 0;
1664        for (int i = 0 ; i < len ; i++) {
1665            char c = a.charAt(i);
1666
1667            switch (state) {
1668                case 0:
1669                    if      (c == '+') state = 1;
1670                    else if (c == '0') state = 2;
1671                    else if (isNonSeparator(c)) return false;
1672                break;
1673
1674                case 2:
1675                    if      (c == '0') state = 3;
1676                    else if (c == '1') state = 4;
1677                    else if (isNonSeparator(c)) return false;
1678                break;
1679
1680                case 4:
1681                    if      (c == '1') state = 5;
1682                    else if (isNonSeparator(c)) return false;
1683                break;
1684
1685                default:
1686                    if (isNonSeparator(c)) return false;
1687                break;
1688
1689            }
1690        }
1691
1692        return state == 1 || state == 3 || state == 5;
1693    }
1694
1695    /** all of 'a' up to len must be a (+|00|011)country code)
1696     *  We're fast and loose with the country code. Any \d{1,3} matches */
1697    private static boolean
1698    matchIntlPrefixAndCC(String a, int len) {
1699        /*  [^0-9*#+pwn]*(\+|0(0|11)\d\d?\d? [^0-9*#+pwn] $ */
1700        /*      0          1 2 3 45  6 7  8                 */
1701
1702        int state = 0;
1703        for (int i = 0 ; i < len ; i++ ) {
1704            char c = a.charAt(i);
1705
1706            switch (state) {
1707                case 0:
1708                    if      (c == '+') state = 1;
1709                    else if (c == '0') state = 2;
1710                    else if (isNonSeparator(c)) return false;
1711                break;
1712
1713                case 2:
1714                    if      (c == '0') state = 3;
1715                    else if (c == '1') state = 4;
1716                    else if (isNonSeparator(c)) return false;
1717                break;
1718
1719                case 4:
1720                    if      (c == '1') state = 5;
1721                    else if (isNonSeparator(c)) return false;
1722                break;
1723
1724                case 1:
1725                case 3:
1726                case 5:
1727                    if      (isISODigit(c)) state = 6;
1728                    else if (isNonSeparator(c)) return false;
1729                break;
1730
1731                case 6:
1732                case 7:
1733                    if      (isISODigit(c)) state++;
1734                    else if (isNonSeparator(c)) return false;
1735                break;
1736
1737                default:
1738                    if (isNonSeparator(c)) return false;
1739            }
1740        }
1741
1742        return state == 6 || state == 7 || state == 8;
1743    }
1744
1745    /** all of 'a' up to len must match non-US trunk prefix ('0') */
1746    private static boolean
1747    matchTrunkPrefix(String a, int len) {
1748        boolean found;
1749
1750        found = false;
1751
1752        for (int i = 0 ; i < len ; i++) {
1753            char c = a.charAt(i);
1754
1755            if (c == '0' && !found) {
1756                found = true;
1757            } else if (isNonSeparator(c)) {
1758                return false;
1759            }
1760        }
1761
1762        return found;
1763    }
1764
1765    //===== End of utility methods used only in compareLoosely() =====
1766
1767    //===== Beggining of utility methods used only in compareStrictly() ====
1768
1769    /*
1770     * If true, the number is country calling code.
1771     */
1772    private static final boolean COUNTLY_CALLING_CALL[] = {
1773        true, true, false, false, false, false, false, true, false, false,
1774        false, false, false, false, false, false, false, false, false, false,
1775        true, false, false, false, false, false, false, true, true, false,
1776        true, true, true, true, true, false, true, false, false, true,
1777        true, false, false, true, true, true, true, true, true, true,
1778        false, true, true, true, true, true, true, true, true, false,
1779        true, true, true, true, true, true, true, false, false, false,
1780        false, false, false, false, false, false, false, false, false, false,
1781        false, true, true, true, true, false, true, false, false, true,
1782        true, true, true, true, true, true, false, false, true, false,
1783    };
1784    private static final int CCC_LENGTH = COUNTLY_CALLING_CALL.length;
1785
1786    /**
1787     * @return true when input is valid Country Calling Code.
1788     */
1789    private static boolean isCountryCallingCode(int countryCallingCodeCandidate) {
1790        return countryCallingCodeCandidate > 0 && countryCallingCodeCandidate < CCC_LENGTH &&
1791                COUNTLY_CALLING_CALL[countryCallingCodeCandidate];
1792    }
1793
1794    /**
1795     * Returns interger corresponding to the input if input "ch" is
1796     * ISO-LATIN characters 0-9.
1797     * Returns -1 otherwise
1798     */
1799    private static int tryGetISODigit(char ch) {
1800        if ('0' <= ch && ch <= '9') {
1801            return ch - '0';
1802        } else {
1803            return -1;
1804        }
1805    }
1806
1807    private static class CountryCallingCodeAndNewIndex {
1808        public final int countryCallingCode;
1809        public final int newIndex;
1810        public CountryCallingCodeAndNewIndex(int countryCode, int newIndex) {
1811            this.countryCallingCode = countryCode;
1812            this.newIndex = newIndex;
1813        }
1814    }
1815
1816    /*
1817     * Note that this function does not strictly care the country calling code with
1818     * 3 length (like Morocco: +212), assuming it is enough to use the first two
1819     * digit to compare two phone numbers.
1820     */
1821    private static CountryCallingCodeAndNewIndex tryGetCountryCallingCodeAndNewIndex(
1822        String str, boolean acceptThailandCase) {
1823        // Rough regexp:
1824        //  ^[^0-9*#+]*((\+|0(0|11)\d\d?|166) [^0-9*#+] $
1825        //         0        1 2 3 45  6 7  89
1826        //
1827        // In all the states, this function ignores separator characters.
1828        // "166" is the special case for the call from Thailand to the US. Uguu!
1829        int state = 0;
1830        int ccc = 0;
1831        final int length = str.length();
1832        for (int i = 0 ; i < length ; i++ ) {
1833            char ch = str.charAt(i);
1834            switch (state) {
1835                case 0:
1836                    if      (ch == '+') state = 1;
1837                    else if (ch == '0') state = 2;
1838                    else if (ch == '1') {
1839                        if (acceptThailandCase) {
1840                            state = 8;
1841                        } else {
1842                            return null;
1843                        }
1844                    } else if (isDialable(ch)) {
1845                        return null;
1846                    }
1847                break;
1848
1849                case 2:
1850                    if      (ch == '0') state = 3;
1851                    else if (ch == '1') state = 4;
1852                    else if (isDialable(ch)) {
1853                        return null;
1854                    }
1855                break;
1856
1857                case 4:
1858                    if      (ch == '1') state = 5;
1859                    else if (isDialable(ch)) {
1860                        return null;
1861                    }
1862                break;
1863
1864                case 1:
1865                case 3:
1866                case 5:
1867                case 6:
1868                case 7:
1869                    {
1870                        int ret = tryGetISODigit(ch);
1871                        if (ret > 0) {
1872                            ccc = ccc * 10 + ret;
1873                            if (ccc >= 100 || isCountryCallingCode(ccc)) {
1874                                return new CountryCallingCodeAndNewIndex(ccc, i + 1);
1875                            }
1876                            if (state == 1 || state == 3 || state == 5) {
1877                                state = 6;
1878                            } else {
1879                                state++;
1880                            }
1881                        } else if (isDialable(ch)) {
1882                            return null;
1883                        }
1884                    }
1885                    break;
1886                case 8:
1887                    if (ch == '6') state = 9;
1888                    else if (isDialable(ch)) {
1889                        return null;
1890                    }
1891                    break;
1892                case 9:
1893                    if (ch == '6') {
1894                        return new CountryCallingCodeAndNewIndex(66, i + 1);
1895                    } else {
1896                        return null;
1897                    }
1898                default:
1899                    return null;
1900            }
1901        }
1902
1903        return null;
1904    }
1905
1906    /**
1907     * Currently this function simply ignore the first digit assuming it is
1908     * trunk prefix. Actually trunk prefix is different in each country.
1909     *
1910     * e.g.
1911     * "+79161234567" equals "89161234567" (Russian trunk digit is 8)
1912     * "+33123456789" equals "0123456789" (French trunk digit is 0)
1913     *
1914     */
1915    private static int tryGetTrunkPrefixOmittedIndex(String str, int currentIndex) {
1916        int length = str.length();
1917        for (int i = currentIndex ; i < length ; i++) {
1918            final char ch = str.charAt(i);
1919            if (tryGetISODigit(ch) >= 0) {
1920                return i + 1;
1921            } else if (isDialable(ch)) {
1922                return -1;
1923            }
1924        }
1925        return -1;
1926    }
1927
1928    /**
1929     * Return true if the prefix of "str" is "ignorable". Here, "ignorable" means
1930     * that "str" has only one digit and separater characters. The one digit is
1931     * assumed to be trunk prefix.
1932     */
1933    private static boolean checkPrefixIsIgnorable(final String str,
1934            int forwardIndex, int backwardIndex) {
1935        boolean trunk_prefix_was_read = false;
1936        while (backwardIndex >= forwardIndex) {
1937            if (tryGetISODigit(str.charAt(backwardIndex)) >= 0) {
1938                if (trunk_prefix_was_read) {
1939                    // More than one digit appeared, meaning that "a" and "b"
1940                    // is different.
1941                    return false;
1942                } else {
1943                    // Ignore just one digit, assuming it is trunk prefix.
1944                    trunk_prefix_was_read = true;
1945                }
1946            } else if (isDialable(str.charAt(backwardIndex))) {
1947                // Trunk prefix is a digit, not "*", "#"...
1948                return false;
1949            }
1950            backwardIndex--;
1951        }
1952
1953        return true;
1954    }
1955
1956    //==== End of utility methods used only in compareStrictly() =====
1957}
1958