PhoneNumberUtils.java revision 3a08cec99e104f74f28ba2463f00c8d4e6d1967e
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.text.Editable;
26import android.text.SpannableStringBuilder;
27import android.text.TextUtils;
28import android.util.SparseIntArray;
29
30import java.util.Locale;
31import java.util.regex.Matcher;
32import java.util.regex.Pattern;
33
34/**
35 * Various utilities for dealing with phone number strings.
36 */
37public class PhoneNumberUtils
38{
39    /*
40     * Special characters
41     *
42     * (See "What is a phone number?" doc)
43     * 'p' --- GSM pause character, same as comma
44     * 'n' --- GSM wild character
45     * 'w' --- GSM wait character
46     */
47    public static final char PAUSE = ',';
48    public static final char WAIT = ';';
49    public static final char WILD = 'N';
50
51    /*
52     * TOA = TON + NPI
53     * See TS 24.008 section 10.5.4.7 for details.
54     * These are the only really useful TOA values
55     */
56    public static final int TOA_International = 0x91;
57    public static final int TOA_Unknown = 0x81;
58
59    /*
60     * global-phone-number = ["+"] 1*( DIGIT / written-sep )
61     * written-sep         = ("-"/".")
62     */
63    private static final Pattern GLOBAL_PHONE_NUMBER_PATTERN =
64            Pattern.compile("[\\+]?[0-9.-]+");
65
66    /** True if c is ISO-LATIN characters 0-9 */
67    public static boolean
68    isISODigit (char c) {
69        return c >= '0' && c <= '9';
70    }
71
72    /** True if c is ISO-LATIN characters 0-9, *, # */
73    public final static boolean
74    is12Key(char c) {
75        return (c >= '0' && c <= '9') || c == '*' || c == '#';
76    }
77
78    /** True if c is ISO-LATIN characters 0-9, *, # , +, WILD  */
79    public final static boolean
80    isDialable(char c) {
81        return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+' || c == WILD;
82    }
83
84    /** True if c is ISO-LATIN characters 0-9, *, # , + (no WILD)  */
85    public final static boolean
86    isReallyDialable(char c) {
87        return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+';
88    }
89
90    /** True if c is ISO-LATIN characters 0-9, *, # , +, WILD, WAIT, PAUSE   */
91    public final static boolean
92    isNonSeparator(char c) {
93        return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+'
94                || c == WILD || c == WAIT || c == PAUSE;
95    }
96
97    /** This any anything to the right of this char is part of the
98     *  post-dial string (eg this is PAUSE or WAIT)
99     */
100    public final static boolean
101    isStartsPostDial (char c) {
102        return c == PAUSE || c == WAIT;
103    }
104
105    /** Extracts the phone number from an Intent.
106     *
107     * @param intent the intent to get the number of
108     * @param context a context to use for database access
109     *
110     * @return the phone number that would be called by the intent, or
111     *         <code>null</code> if the number cannot be found.
112     */
113    public static String getNumberFromIntent(Intent intent, Context context) {
114        String number = null;
115
116        Uri uri = intent.getData();
117        String scheme = uri.getScheme();
118
119        if (scheme.equals("tel")) {
120            return uri.getSchemeSpecificPart();
121        }
122
123        if (scheme.equals("voicemail")) {
124            return TelephonyManager.getDefault().getVoiceMailNumber();
125        }
126
127        if (context == null) {
128            return null;
129        }
130
131        String type = intent.resolveType(context);
132
133        Cursor c = context.getContentResolver().query(
134                uri, new String[]{ Contacts.People.Phones.NUMBER },
135                null, null, null);
136        if (c != null) {
137            try {
138                if (c.moveToFirst()) {
139                    number = c.getString(
140                            c.getColumnIndex(Contacts.People.Phones.NUMBER));
141                }
142            } finally {
143                c.close();
144            }
145        }
146
147        return number;
148    }
149
150    /** Extracts the network address portion and canonicalizes
151     *  (filters out separators.)
152     *  Network address portion is everything up to DTMF control digit
153     *  separators (pause or wait), but without non-dialable characters.
154     *
155     *  Please note that the GSM wild character is allowed in the result.
156     *  This must be resolved before dialing.
157     *
158     *  Allows + only in the first  position in the result string.
159     *
160     *  Returns null if phoneNumber == null
161     */
162    public static String
163    extractNetworkPortion(String phoneNumber) {
164        if (phoneNumber == null) {
165            return null;
166        }
167
168        int len = phoneNumber.length();
169        StringBuilder ret = new StringBuilder(len);
170        boolean firstCharAdded = false;
171
172        for (int i = 0; i < len; i++) {
173            char c = phoneNumber.charAt(i);
174            if (isDialable(c) && (c != '+' || !firstCharAdded)) {
175                firstCharAdded = true;
176                ret.append(c);
177            } else if (isStartsPostDial (c)) {
178                break;
179            }
180        }
181
182        return ret.toString();
183    }
184
185    /**
186     * Strips separators from a phone number string.
187     * @param phoneNumber phone number to strip.
188     * @return phone string stripped of separators.
189     */
190    public static String stripSeparators(String phoneNumber) {
191        if (phoneNumber == null) {
192            return null;
193        }
194        int len = phoneNumber.length();
195        StringBuilder ret = new StringBuilder(len);
196
197        for (int i = 0; i < len; i++) {
198            char c = phoneNumber.charAt(i);
199            if (isNonSeparator(c)) {
200                ret.append(c);
201            }
202        }
203
204        return ret.toString();
205    }
206
207    /** or -1 if both are negative */
208    static private int
209    minPositive (int a, int b) {
210        if (a >= 0 && b >= 0) {
211            return (a < b) ? a : b;
212        } else if (a >= 0) { /* && b < 0 */
213            return a;
214        } else if (b >= 0) { /* && a < 0 */
215            return b;
216        } else { /* a < 0 && b < 0 */
217            return -1;
218        }
219    }
220
221    /** index of the last character of the network portion
222     *  (eg anything after is a post-dial string)
223     */
224    static private int
225    indexOfLastNetworkChar(String a) {
226        int pIndex, wIndex;
227        int origLength;
228        int trimIndex;
229
230        origLength = a.length();
231
232        pIndex = a.indexOf(PAUSE);
233        wIndex = a.indexOf(WAIT);
234
235        trimIndex = minPositive(pIndex, wIndex);
236
237        if (trimIndex < 0) {
238            return origLength - 1;
239        } else {
240            return trimIndex - 1;
241        }
242    }
243
244    /**
245     * Extracts the post-dial sequence of DTMF control digits, pauses, and
246     * waits. Strips separators. This string may be empty, but will not be null
247     * unless phoneNumber == null.
248     *
249     * Returns null if phoneNumber == null
250     */
251
252    public static String
253    extractPostDialPortion(String phoneNumber) {
254        if (phoneNumber == null) return null;
255
256        int trimIndex;
257        StringBuilder ret = new StringBuilder();
258
259        trimIndex = indexOfLastNetworkChar (phoneNumber);
260
261        for (int i = trimIndex + 1, s = phoneNumber.length()
262                ; i < s; i++
263        ) {
264            char c = phoneNumber.charAt(i);
265            if (isNonSeparator(c)) {
266                ret.append(c);
267            }
268        }
269
270        return ret.toString();
271    }
272
273    /**
274     * Compare phone numbers a and b, return true if they're identical
275     * enough for caller ID purposes.
276     *
277     * - Compares from right to left
278     * - requires MIN_MATCH (5) characters to match
279     * - handles common trunk prefixes and international prefixes
280     *   (basically, everything except the Russian trunk prefix)
281     *
282     * Tolerates nulls
283     */
284    public static boolean
285    compare(String a, String b) {
286        int ia, ib;
287        int matched;
288
289        if (a == null || b == null) return a == b;
290
291        if (a.length() == 0 || b.length() == 0) {
292            return false;
293        }
294
295        ia = indexOfLastNetworkChar (a);
296        ib = indexOfLastNetworkChar (b);
297        matched = 0;
298
299        while (ia >= 0 && ib >=0) {
300            char ca, cb;
301            boolean skipCmp = false;
302
303            ca = a.charAt(ia);
304
305            if (!isDialable(ca)) {
306                ia--;
307                skipCmp = true;
308            }
309
310            cb = b.charAt(ib);
311
312            if (!isDialable(cb)) {
313                ib--;
314                skipCmp = true;
315            }
316
317            if (!skipCmp) {
318                if (cb != ca && ca != WILD && cb != WILD) {
319                    break;
320                }
321                ia--; ib--; matched++;
322            }
323        }
324
325        if (matched < MIN_MATCH) {
326            int aLen = a.length();
327
328            // if the input strings match, but their lengths < MIN_MATCH,
329            // treat them as equal.
330            if (aLen == b.length() && aLen == matched) {
331                return true;
332            }
333            return false;
334        }
335
336        // At least one string has matched completely;
337        if (matched >= MIN_MATCH && (ia < 0 || ib < 0)) {
338            return true;
339        }
340
341        /*
342         * Now, what remains must be one of the following for a
343         * match:
344         *
345         *  - a '+' on one and a '00' or a '011' on the other
346         *  - a '0' on one and a (+,00)<country code> on the other
347         *     (for this, a '0' and a '00' prefix would have succeeded above)
348         */
349
350        if (matchIntlPrefix(a, ia + 1)
351            && matchIntlPrefix (b, ib +1)
352        ) {
353            return true;
354        }
355
356        if (matchTrunkPrefix(a, ia + 1)
357            && matchIntlPrefixAndCC(b, ib +1)
358        ) {
359            return true;
360        }
361
362        if (matchTrunkPrefix(b, ib + 1)
363            && matchIntlPrefixAndCC(a, ia +1)
364        ) {
365            return true;
366        }
367
368        return false;
369    }
370
371    /**
372     * Returns the rightmost MIN_MATCH (5) characters in the network portion
373     * in *reversed* order
374     *
375     * This can be used to do a database lookup against the column
376     * that stores getStrippedReversed()
377     *
378     * Returns null if phoneNumber == null
379     */
380    public static String
381    toCallerIDMinMatch(String phoneNumber) {
382        String np = extractNetworkPortion(phoneNumber);
383        return internalGetStrippedReversed(np, MIN_MATCH);
384    }
385
386    /**
387     * Returns the network portion reversed.
388     * This string is intended to go into an index column for a
389     * database lookup.
390     *
391     * Returns null if phoneNumber == null
392     */
393    public static String
394    getStrippedReversed(String phoneNumber) {
395        String np = extractNetworkPortion(phoneNumber);
396
397        if (np == null) return null;
398
399        return internalGetStrippedReversed(np, np.length());
400    }
401
402    /**
403     * Returns the last numDigits of the reversed phone number
404     * Returns null if np == null
405     */
406    private static String
407    internalGetStrippedReversed(String np, int numDigits) {
408        if (np == null) return null;
409
410        StringBuilder ret = new StringBuilder(numDigits);
411        int length = np.length();
412
413        for (int i = length - 1, s = length
414            ; i >= 0 && (s - i) <= numDigits ; i--
415        ) {
416            char c = np.charAt(i);
417
418            ret.append(c);
419        }
420
421        return ret.toString();
422    }
423
424    /**
425     * Basically: makes sure there's a + in front of a
426     * TOA_International number
427     *
428     * Returns null if s == null
429     */
430    public static String
431    stringFromStringAndTOA(String s, int TOA) {
432        if (s == null) return null;
433
434        if (TOA == TOA_International && s.length() > 0 && s.charAt(0) != '+') {
435            return "+" + s;
436        }
437
438        return s;
439    }
440
441    /**
442     * Returns the TOA for the given dial string
443     * Basically, returns TOA_International if there's a + prefix
444     */
445
446    public static int
447    toaFromString(String s) {
448        if (s != null && s.length() > 0 && s.charAt(0) == '+') {
449            return TOA_International;
450        }
451
452        return TOA_Unknown;
453    }
454
455    /**
456     * Phone numbers are stored in "lookup" form in the database
457     * as reversed strings to allow for caller ID lookup
458     *
459     * This method takes a phone number and makes a valid SQL "LIKE"
460     * string that will match the lookup form
461     *
462     */
463    /** all of a up to len must be an international prefix or
464     *  separators/non-dialing digits
465     */
466    private static boolean
467    matchIntlPrefix(String a, int len) {
468        /* '([^0-9*#+pwn]\+[^0-9*#+pwn] | [^0-9*#+pwn]0(0|11)[^0-9*#+pwn] )$' */
469        /*        0       1                           2 3 45               */
470
471        int state = 0;
472        for (int i = 0 ; i < len ; i++) {
473            char c = a.charAt(i);
474
475            switch (state) {
476                case 0:
477                    if      (c == '+') state = 1;
478                    else if (c == '0') state = 2;
479                    else if (isNonSeparator(c)) return false;
480                break;
481
482                case 2:
483                    if      (c == '0') state = 3;
484                    else if (c == '1') state = 4;
485                    else if (isNonSeparator(c)) return false;
486                break;
487
488                case 4:
489                    if      (c == '1') state = 5;
490                    else if (isNonSeparator(c)) return false;
491                break;
492
493                default:
494                    if (isNonSeparator(c)) return false;
495                break;
496
497            }
498        }
499
500        return state == 1 || state == 3 || state == 5;
501    }
502
503    /**
504     *  3GPP TS 24.008 10.5.4.7
505     *  Called Party BCD Number
506     *
507     *  See Also TS 51.011 10.5.1 "dialing number/ssc string"
508     *  and TS 11.11 "10.3.1 EF adn (Abbreviated dialing numbers)"
509     *
510     * @param bytes the data buffer
511     * @param offset should point to the TOA (aka. TON/NPI) octet after the length byte
512     * @param length is the number of bytes including TOA byte
513     *                and must be at least 2
514     *
515     * @return partial string on invalid decode
516     *
517     * FIXME(mkf) support alphanumeric address type
518     *  currently implemented in SMSMessage.getAddress()
519     */
520    public static String
521    calledPartyBCDToString (byte[] bytes, int offset, int length) {
522        boolean prependPlus = false;
523        StringBuilder ret = new StringBuilder(1 + length * 2);
524
525        if (length < 2) {
526            return "";
527        }
528
529        if ((bytes[offset] & 0xff) == TOA_International) {
530            prependPlus = true;
531        }
532
533        internalCalledPartyBCDFragmentToString(
534                ret, bytes, offset + 1, length - 1);
535
536        if (prependPlus && ret.length() == 0) {
537            // If the only thing there is a prepended plus, return ""
538            return "";
539        }
540
541        if (prependPlus) {
542            // This is an "international number" and should have
543            // a plus prepended to the dialing number. But there
544            // can also be Gsm MMI codes as defined in TS 22.030 6.5.2
545            // so we need to handle those also.
546            //
547            // http://web.telia.com/~u47904776/gsmkode.htm is a
548            // has a nice list of some of these GSM codes.
549            //
550            // Examples are:
551            //   **21*+886988171479#
552            //   **21*8311234567#
553            //   *21#
554            //   #21#
555            //   *#21#
556            //   *31#+11234567890
557            //   #31#+18311234567
558            //   #31#8311234567
559            //   18311234567
560            //   +18311234567#
561            //   +18311234567
562            // Odd ball cases that some phones handled
563            // where there is no dialing number so they
564            // append the "+"
565            //   *21#+
566            //   **21#+
567            String retString = ret.toString();
568            Pattern p = Pattern.compile("(^[#*])(.*)([#*])(.*)(#)$");
569            Matcher m = p.matcher(retString);
570            if (m.matches()) {
571                if ("".equals(m.group(2))) {
572                    // Started with two [#*] ends with #
573                    // So no dialing number and we'll just
574                    // append a +, this handles **21#+
575                    ret = new StringBuilder();
576                    ret.append(m.group(1));
577                    ret.append(m.group(3));
578                    ret.append(m.group(4));
579                    ret.append(m.group(5));
580                    ret.append("+");
581                } else {
582                    // Starts with [#*] and ends with #
583                    // Assume group 4 is a dialing number
584                    // such as *21*+1234554#
585                    ret = new StringBuilder();
586                    ret.append(m.group(1));
587                    ret.append(m.group(2));
588                    ret.append(m.group(3));
589                    ret.append("+");
590                    ret.append(m.group(4));
591                    ret.append(m.group(5));
592                }
593            } else {
594                p = Pattern.compile("(^[#*])(.*)([#*])(.*)");
595                m = p.matcher(retString);
596                if (m.matches()) {
597                    // Starts with [#*] and only one other [#*]
598                    // Assume the data after last [#*] is dialing
599                    // number (i.e. group 4) such as *31#+11234567890.
600                    // This also includes the odd ball *21#+
601                    ret = new StringBuilder();
602                    ret.append(m.group(1));
603                    ret.append(m.group(2));
604                    ret.append(m.group(3));
605                    ret.append("+");
606                    ret.append(m.group(4));
607                } else {
608                    // Does NOT start with [#*] just prepend '+'
609                    ret = new StringBuilder();
610                    ret.append('+');
611                    ret.append(retString);
612                }
613            }
614        }
615
616        return ret.toString();
617    }
618
619    private static void
620    internalCalledPartyBCDFragmentToString(
621        StringBuilder sb, byte [] bytes, int offset, int length) {
622        for (int i = offset ; i < length + offset ; i++) {
623            byte b;
624            char c;
625
626            c = bcdToChar((byte)(bytes[i] & 0xf));
627
628            if (c == 0) {
629                return;
630            }
631            sb.append(c);
632
633            // FIXME(mkf) TS 23.040 9.1.2.3 says
634            // "if a mobile receives 1111 in a position prior to
635            // the last semi-octet then processing shall commense with
636            // the next semi-octet and the intervening
637            // semi-octet shall be ignored"
638            // How does this jive with 24,008 10.5.4.7
639
640            b = (byte)((bytes[i] >> 4) & 0xf);
641
642            if (b == 0xf && i + 1 == length + offset) {
643                //ignore final 0xf
644                break;
645            }
646
647            c = bcdToChar(b);
648            if (c == 0) {
649                return;
650            }
651
652            sb.append(c);
653        }
654
655    }
656
657    /**
658     * Like calledPartyBCDToString, but field does not start with a
659     * TOA byte. For example: SIM ADN extension fields
660     */
661
662    public static String
663    calledPartyBCDFragmentToString(byte [] bytes, int offset, int length) {
664        StringBuilder ret = new StringBuilder(length * 2);
665
666        internalCalledPartyBCDFragmentToString(ret, bytes, offset, length);
667
668        return ret.toString();
669    }
670
671    /** returns 0 on invalid value */
672    private static char
673    bcdToChar(byte b) {
674        if (b < 0xa) {
675            return (char)('0' + b);
676        } else switch (b) {
677            case 0xa: return '*';
678            case 0xb: return '#';
679            case 0xc: return PAUSE;
680            case 0xd: return WILD;
681
682            default: return 0;
683        }
684    }
685
686    private static int
687    charToBCD(char c) {
688        if (c >= '0' && c <= '9') {
689            return c - '0';
690        } else if (c == '*') {
691            return 0xa;
692        } else if (c == '#') {
693            return 0xb;
694        } else if (c == PAUSE) {
695            return 0xc;
696        } else if (c == WILD) {
697            return 0xd;
698        } else {
699            throw new RuntimeException ("invalid char for BCD " + c);
700        }
701    }
702
703    /**
704     * Return true iff the network portion of <code>address</code> is,
705     * as far as we can tell on the device, suitable for use as an SMS
706     * destination address.
707     */
708    public static boolean isWellFormedSmsAddress(String address) {
709        String networkPortion =
710                PhoneNumberUtils.extractNetworkPortion(address);
711
712        return (!(networkPortion.equals("+")
713                  || TextUtils.isEmpty(networkPortion)))
714               && isDialable(networkPortion);
715    }
716
717    public static boolean isGlobalPhoneNumber(String phoneNumber) {
718        if (TextUtils.isEmpty(phoneNumber)) {
719            return false;
720        }
721
722        Matcher match = GLOBAL_PHONE_NUMBER_PATTERN.matcher(phoneNumber);
723        return match.matches();
724    }
725
726    private static boolean isDialable(String address) {
727        for (int i = 0, count = address.length(); i < count; i++) {
728            if (!isDialable(address.charAt(i))) {
729                return false;
730            }
731        }
732        return true;
733    }
734
735    /**
736     * Note: calls extractNetworkPortion(), so do not use for
737     * SIM EF[ADN] style records
738     *
739     * Returns null if network portion is empty.
740     */
741    public static byte[]
742    networkPortionToCalledPartyBCD(String s) {
743        String networkPortion = extractNetworkPortion(s);
744        return numberToCalledPartyBCDHelper(networkPortion, false);
745    }
746
747    /**
748     * Same as {@link #networkPortionToCalledPartyBCD}, but includes a
749     * one-byte length prefix.
750     */
751    public static byte[]
752    networkPortionToCalledPartyBCDWithLength(String s) {
753        String networkPortion = extractNetworkPortion(s);
754        return numberToCalledPartyBCDHelper(networkPortion, true);
755    }
756
757    /**
758     * Convert a dialing number to BCD byte array
759     *
760     * @param number dialing number string
761     *        if the dialing number starts with '+', set to internationl TOA
762     * @return BCD byte array
763     */
764    public static byte[]
765    numberToCalledPartyBCD(String number) {
766        return numberToCalledPartyBCDHelper(number, false);
767    }
768
769    /**
770     * If includeLength is true, prepend a one-byte length value to
771     * the return array.
772     */
773    private static byte[]
774    numberToCalledPartyBCDHelper(String number, boolean includeLength) {
775        int numberLenReal = number.length();
776        int numberLenEffective = numberLenReal;
777        boolean hasPlus = number.indexOf('+') != -1;
778        if (hasPlus) numberLenEffective--;
779
780        if (numberLenEffective == 0) return null;
781
782        int resultLen = (numberLenEffective + 1) / 2;  // Encoded numbers require only 4 bits each.
783        int extraBytes = 1;                            // Prepended TOA byte.
784        if (includeLength) extraBytes++;               // Optional prepended length byte.
785        resultLen += extraBytes;
786
787        byte[] result = new byte[resultLen];
788
789        int digitCount = 0;
790        for (int i = 0; i < numberLenReal; i++) {
791            char c = number.charAt(i);
792            if (c == '+') continue;
793            int shift = ((digitCount & 0x01) == 1) ? 4 : 0;
794            result[extraBytes + (digitCount >> 1)] |= (byte)((charToBCD(c) & 0x0F) << shift);
795            digitCount++;
796        }
797
798        // 1-fill any trailing odd nibble/quartet.
799        if ((digitCount & 0x01) == 1) result[extraBytes + (digitCount >> 1)] |= 0xF0;
800
801        int offset = 0;
802        if (includeLength) result[offset++] = (byte)(resultLen - 1);
803        result[offset] = (byte)(hasPlus ? TOA_International : TOA_Unknown);
804
805        return result;
806    }
807
808    /** all of 'a' up to len must match non-US trunk prefix ('0') */
809    private static boolean
810    matchTrunkPrefix(String a, int len) {
811        boolean found;
812
813        found = false;
814
815        for (int i = 0 ; i < len ; i++) {
816            char c = a.charAt(i);
817
818            if (c == '0' && !found) {
819                found = true;
820            } else if (isNonSeparator(c)) {
821                return false;
822            }
823        }
824
825        return found;
826    }
827
828    /** all of 'a' up to len must be a (+|00|011)country code)
829     *  We're fast and loose with the country code. Any \d{1,3} matches */
830    private static boolean
831    matchIntlPrefixAndCC(String a, int len) {
832        /*  [^0-9*#+pwn]*(\+|0(0|11)\d\d?\d? [^0-9*#+pwn] $ */
833        /*      0          1 2 3 45  6 7  8                 */
834
835        int state = 0;
836        for (int i = 0 ; i < len ; i++ ) {
837            char c = a.charAt(i);
838
839            switch (state) {
840                case 0:
841                    if      (c == '+') state = 1;
842                    else if (c == '0') state = 2;
843                    else if (isNonSeparator(c)) return false;
844                break;
845
846                case 2:
847                    if      (c == '0') state = 3;
848                    else if (c == '1') state = 4;
849                    else if (isNonSeparator(c)) return false;
850                break;
851
852                case 4:
853                    if      (c == '1') state = 5;
854                    else if (isNonSeparator(c)) return false;
855                break;
856
857                case 1:
858                case 3:
859                case 5:
860                    if      (isISODigit(c)) state = 6;
861                    else if (isNonSeparator(c)) return false;
862                break;
863
864                case 6:
865                case 7:
866                    if      (isISODigit(c)) state++;
867                    else if (isNonSeparator(c)) return false;
868                break;
869
870                default:
871                    if (isNonSeparator(c)) return false;
872            }
873        }
874
875        return state == 6 || state == 7 || state == 8;
876    }
877
878    //================ Number formatting =========================
879
880    /** The current locale is unknown, look for a country code or don't format */
881    public static final int FORMAT_UNKNOWN = 0;
882    /** NANP formatting */
883    public static final int FORMAT_NANP = 1;
884    /** Japanese formatting */
885    public static final int FORMAT_JAPAN = 2;
886
887    /** List of country codes for countries that use the NANP */
888    private static final String[] NANP_COUNTRIES = new String[] {
889        "US", // United States
890        "CA", // Canada
891        "AS", // American Samoa
892        "AI", // Anguilla
893        "AG", // Antigua and Barbuda
894        "BS", // Bahamas
895        "BB", // Barbados
896        "BM", // Bermuda
897        "VG", // British Virgin Islands
898        "KY", // Cayman Islands
899        "DM", // Dominica
900        "DO", // Dominican Republic
901        "GD", // Grenada
902        "GU", // Guam
903        "JM", // Jamaica
904        "PR", // Puerto Rico
905        "MS", // Montserrat
906        "NP", // Northern Mariana Islands
907        "KN", // Saint Kitts and Nevis
908        "LC", // Saint Lucia
909        "VC", // Saint Vincent and the Grenadines
910        "TT", // Trinidad and Tobago
911        "TC", // Turks and Caicos Islands
912        "VI", // U.S. Virgin Islands
913    };
914
915    /**
916     * Breaks the given number down and formats it according to the rules
917     * for the country the number is from.
918     *
919     * @param source the phone number to format
920     * @return a locally acceptable formatting of the input, or the raw input if
921     *  formatting rules aren't known for the number
922     */
923    public static String formatNumber(String source) {
924        SpannableStringBuilder text = new SpannableStringBuilder(source);
925        formatNumber(text, getFormatTypeForLocale(Locale.getDefault()));
926        return text.toString();
927    }
928
929    /**
930     * Returns the phone number formatting type for the given locale.
931     *
932     * @param locale The locale of interest, usually {@link Locale#getDefault()}
933     * @return the formatting type for the given locale, or FORMAT_UNKNOWN if the formatting
934     * rules are not known for the given locale
935     */
936    public static int getFormatTypeForLocale(Locale locale) {
937        String country = locale.getCountry();
938
939        // Check for the NANP countries
940        int length = NANP_COUNTRIES.length;
941        for (int i = 0; i < length; i++) {
942            if (NANP_COUNTRIES[i].equals(country)) {
943                return FORMAT_NANP;
944            }
945        }
946        if (locale.equals(Locale.JAPAN)) {
947            return FORMAT_JAPAN;
948        }
949        return FORMAT_UNKNOWN;
950    }
951
952    /**
953     * Formats a phone number in-place. Currently only supports NANP formatting.
954     *
955     * @param text The number to be formatted, will be modified with the formatting
956     * @param defaultFormattingType The default formatting rules to apply if the number does
957     * not begin with +<country_code>
958     */
959    public static void formatNumber(Editable text, int defaultFormattingType) {
960        int formatType = defaultFormattingType;
961
962        if (text.length() > 2 && text.charAt(0) == '+') {
963            if (text.charAt(1) == '1') {
964                formatType = FORMAT_NANP;
965            } else if (text.length() >= 3 && text.charAt(1) == '8'
966                && text.charAt(2) == '1') {
967                formatType = FORMAT_JAPAN;
968            } else {
969                return;
970            }
971        }
972
973        switch (formatType) {
974            case FORMAT_NANP:
975                formatNanpNumber(text);
976                return;
977            case FORMAT_JAPAN:
978                formatJapaneseNumber(text);
979                return;
980        }
981    }
982
983    private static final int NANP_STATE_DIGIT = 1;
984    private static final int NANP_STATE_PLUS = 2;
985    private static final int NANP_STATE_ONE = 3;
986    private static final int NANP_STATE_DASH = 4;
987
988    /**
989     * Formats a phone number in-place using the NANP formatting rules. Numbers will be formatted
990     * as:
991     *
992     * <p><code>
993     * xxx-xxxx
994     * xxx-xxx-xxxx
995     * 1-xxx-xxx-xxxx
996     * +1-xxx-xxx-xxxx
997     * </code></p>
998     *
999     * @param text the number to be formatted, will be modified with the formatting
1000     */
1001    public static void formatNanpNumber(Editable text) {
1002        int length = text.length();
1003        if (length > "+1-nnn-nnn-nnnn".length()) {
1004            // The string is too long to be formatted
1005            return;
1006        }
1007        CharSequence saved = text.subSequence(0, length);
1008
1009        // Strip the dashes first, as we're going to add them back
1010        int p = 0;
1011        while (p < text.length()) {
1012            if (text.charAt(p) == '-') {
1013                text.delete(p, p + 1);
1014            } else {
1015                p++;
1016            }
1017        }
1018        length = text.length();
1019
1020        // When scanning the number we record where dashes need to be added,
1021        // if they're non-0 at the end of the scan the dashes will be added in
1022        // the proper places.
1023        int dashPositions[] = new int[3];
1024        int numDashes = 0;
1025
1026        int state = NANP_STATE_DIGIT;
1027        int numDigits = 0;
1028        for (int i = 0; i < length; i++) {
1029            char c = text.charAt(i);
1030            switch (c) {
1031                case '1':
1032                    if (numDigits == 0 || state == NANP_STATE_PLUS) {
1033                        state = NANP_STATE_ONE;
1034                        break;
1035                    }
1036                    // fall through
1037                case '2':
1038                case '3':
1039                case '4':
1040                case '5':
1041                case '6':
1042                case '7':
1043                case '8':
1044                case '9':
1045                case '0':
1046                    if (state == NANP_STATE_PLUS) {
1047                        // Only NANP number supported for now
1048                        text.replace(0, length, saved);
1049                        return;
1050                    } else if (state == NANP_STATE_ONE) {
1051                        // Found either +1 or 1, follow it up with a dash
1052                        dashPositions[numDashes++] = i;
1053                    } else if (state != NANP_STATE_DASH && (numDigits == 3 || numDigits == 6)) {
1054                        // Found a digit that should be after a dash that isn't
1055                        dashPositions[numDashes++] = i;
1056                    }
1057                    state = NANP_STATE_DIGIT;
1058                    numDigits++;
1059                    break;
1060
1061                case '-':
1062                    state = NANP_STATE_DASH;
1063                    break;
1064
1065                case '+':
1066                    if (i == 0) {
1067                        // Plus is only allowed as the first character
1068                        state = NANP_STATE_PLUS;
1069                        break;
1070                    }
1071                    // Fall through
1072                default:
1073                    // Unknown character, bail on formatting
1074                    text.replace(0, length, saved);
1075                    return;
1076            }
1077        }
1078
1079        if (numDigits == 7) {
1080            // With 7 digits we want xxx-xxxx, not xxx-xxx-x
1081            numDashes--;
1082        }
1083
1084        // Actually put the dashes in place
1085        for (int i = 0; i < numDashes; i++) {
1086            int pos = dashPositions[i];
1087            text.replace(pos + i, pos + i, "-");
1088        }
1089
1090        // Remove trailing dashes
1091        int len = text.length();
1092        while (len > 0) {
1093            if (text.charAt(len - 1) == '-') {
1094                text.delete(len - 1, len);
1095                len--;
1096            } else {
1097                break;
1098            }
1099        }
1100    }
1101
1102    /**
1103     * Formats a phone number in-place using the Japanese formatting rules.
1104     * Numbers will be formatted as:
1105     *
1106     * <p><code>
1107     * 03-xxxx-xxxx
1108     * 090-xxxx-xxxx
1109     * 0120-xxx-xxx
1110     * +81-3-xxxx-xxxx
1111     * +81-90-xxxx-xxxx
1112     * </code></p>
1113     *
1114     * @param text the number to be formatted, will be modified with
1115     * the formatting
1116     */
1117    public static void formatJapaneseNumber(Editable text) {
1118        JapanesePhoneNumberFormatter.format(text);
1119    }
1120
1121    // Three and four digit phone numbers for either special services
1122    // or from the network (eg carrier-originated SMS messages) should
1123    // not match
1124    static final int MIN_MATCH = 5;
1125
1126    /**
1127     * isEmergencyNumber: checks a given number against the list of
1128     *   emergency numbers provided by the RIL and SIM card.
1129     *
1130     * @param number the number to look up.
1131     * @return if the number is in the list of emergency numbers
1132     * listed in the ril / sim, then return true, otherwise false.
1133     */
1134    public static boolean isEmergencyNumber(String number) {
1135        // Strip the separators from the number before comparing it
1136        // to the list.
1137        number = extractNetworkPortion(number);
1138
1139        // retrieve the list of emergency numbers
1140        String numbers = SystemProperties.get("ro.ril.ecclist");
1141
1142        if (!TextUtils.isEmpty(numbers)) {
1143            // searches through the comma-separated list for a match,
1144            // return true if one is found.
1145            for (String emergencyNum : numbers.split(",")) {
1146                if (emergencyNum.equals(number)) {
1147                    return true;
1148                }
1149            }
1150            // no matches found against the list!
1151            return false;
1152        }
1153
1154        //no ecclist system property, so use our own list.
1155        return (number.equals("112") || number.equals("911"));
1156    }
1157
1158    /**
1159     * Translates any alphabetic letters (i.e. [A-Za-z]) in the
1160     * specified phone number into the equivalent numeric digits,
1161     * according to the phone keypad letter mapping described in
1162     * ITU E.161 and ISO/IEC 9995-8.
1163     *
1164     * @return the input string, with alpha letters converted to numeric
1165     *         digits using the phone keypad letter mapping.  For example,
1166     *         an input of "1-800-GOOG-411" will return "1-800-4664-411".
1167     */
1168    public static String convertKeypadLettersToDigits(String input) {
1169        if (input == null) {
1170            return input;
1171        }
1172        int len = input.length();
1173        if (len == 0) {
1174            return input;
1175        }
1176
1177        char[] out = input.toCharArray();
1178
1179        for (int i = 0; i < len; i++) {
1180            char c = out[i];
1181            // If this char isn't in KEYPAD_MAP at all, just leave it alone.
1182            out[i] = (char) KEYPAD_MAP.get(c, c);
1183        }
1184
1185        return new String(out);
1186    }
1187
1188    /**
1189     * The phone keypad letter mapping (see ITU E.161 or ISO/IEC 9995-8.)
1190     * TODO: This should come from a resource.
1191     */
1192    private static final SparseIntArray KEYPAD_MAP = new SparseIntArray();
1193    static {
1194        KEYPAD_MAP.put('a', '2'); KEYPAD_MAP.put('b', '2'); KEYPAD_MAP.put('c', '2');
1195        KEYPAD_MAP.put('A', '2'); KEYPAD_MAP.put('B', '2'); KEYPAD_MAP.put('C', '2');
1196
1197        KEYPAD_MAP.put('d', '3'); KEYPAD_MAP.put('e', '3'); KEYPAD_MAP.put('f', '3');
1198        KEYPAD_MAP.put('D', '3'); KEYPAD_MAP.put('E', '3'); KEYPAD_MAP.put('F', '3');
1199
1200        KEYPAD_MAP.put('g', '4'); KEYPAD_MAP.put('h', '4'); KEYPAD_MAP.put('i', '4');
1201        KEYPAD_MAP.put('G', '4'); KEYPAD_MAP.put('H', '4'); KEYPAD_MAP.put('I', '4');
1202
1203        KEYPAD_MAP.put('j', '5'); KEYPAD_MAP.put('k', '5'); KEYPAD_MAP.put('l', '5');
1204        KEYPAD_MAP.put('J', '5'); KEYPAD_MAP.put('K', '5'); KEYPAD_MAP.put('L', '5');
1205
1206        KEYPAD_MAP.put('m', '6'); KEYPAD_MAP.put('n', '6'); KEYPAD_MAP.put('o', '6');
1207        KEYPAD_MAP.put('M', '6'); KEYPAD_MAP.put('N', '6'); KEYPAD_MAP.put('O', '6');
1208
1209        KEYPAD_MAP.put('p', '7'); KEYPAD_MAP.put('q', '7'); KEYPAD_MAP.put('r', '7'); KEYPAD_MAP.put('s', '7');
1210        KEYPAD_MAP.put('P', '7'); KEYPAD_MAP.put('Q', '7'); KEYPAD_MAP.put('R', '7'); KEYPAD_MAP.put('S', '7');
1211
1212        KEYPAD_MAP.put('t', '8'); KEYPAD_MAP.put('u', '8'); KEYPAD_MAP.put('v', '8');
1213        KEYPAD_MAP.put('T', '8'); KEYPAD_MAP.put('U', '8'); KEYPAD_MAP.put('V', '8');
1214
1215        KEYPAD_MAP.put('w', '9'); KEYPAD_MAP.put('x', '9'); KEYPAD_MAP.put('y', '9'); KEYPAD_MAP.put('z', '9');
1216        KEYPAD_MAP.put('W', '9'); KEYPAD_MAP.put('X', '9'); KEYPAD_MAP.put('Y', '9'); KEYPAD_MAP.put('Z', '9');
1217    }
1218}
1219