GsmMmiCode.java revision d720945f2be5ea5fe0faf67e67d9ea0e184eba67
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 com.android.internal.telephony.gsm;
18
19import android.content.Context;
20import com.android.internal.telephony.*;
21import com.android.internal.telephony.uicc.IccRecords;
22import com.android.internal.telephony.uicc.UiccCardApplication;
23import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppState;
24
25import android.os.*;
26import android.telephony.PhoneNumberUtils;
27import android.text.SpannableStringBuilder;
28import android.text.TextUtils;
29import android.telephony.Rlog;
30
31import static com.android.internal.telephony.CommandsInterface.*;
32
33import java.util.regex.Pattern;
34import java.util.regex.Matcher;
35
36/**
37 * The motto for this file is:
38 *
39 * "NOTE:    By using the # as a separator, most cases are expected to be unambiguous."
40 *   -- TS 22.030 6.5.2
41 *
42 * {@hide}
43 *
44 */
45public final class GsmMmiCode extends Handler implements MmiCode {
46    static final String LOG_TAG = "GSM";
47
48    //***** Constants
49
50    // Max Size of the Short Code (aka Short String from TS 22.030 6.5.2)
51    static final int MAX_LENGTH_SHORT_CODE = 2;
52
53    // TS 22.030 6.5.2 Every Short String USSD command will end with #-key
54    // (known as #-String)
55    static final char END_OF_USSD_COMMAND = '#';
56
57    // From TS 22.030 6.5.2
58    static final String ACTION_ACTIVATE = "*";
59    static final String ACTION_DEACTIVATE = "#";
60    static final String ACTION_INTERROGATE = "*#";
61    static final String ACTION_REGISTER = "**";
62    static final String ACTION_ERASURE = "##";
63
64    // Supp Service codes from TS 22.030 Annex B
65
66    //Called line presentation
67    static final String SC_CLIP    = "30";
68    static final String SC_CLIR    = "31";
69
70    // Call Forwarding
71    static final String SC_CFU     = "21";
72    static final String SC_CFB     = "67";
73    static final String SC_CFNRy   = "61";
74    static final String SC_CFNR    = "62";
75
76    static final String SC_CF_All = "002";
77    static final String SC_CF_All_Conditional = "004";
78
79    // Call Waiting
80    static final String SC_WAIT     = "43";
81
82    // Call Barring
83    static final String SC_BAOC         = "33";
84    static final String SC_BAOIC        = "331";
85    static final String SC_BAOICxH      = "332";
86    static final String SC_BAIC         = "35";
87    static final String SC_BAICr        = "351";
88
89    static final String SC_BA_ALL       = "330";
90    static final String SC_BA_MO        = "333";
91    static final String SC_BA_MT        = "353";
92
93    // Supp Service Password registration
94    static final String SC_PWD          = "03";
95
96    // PIN/PIN2/PUK/PUK2
97    static final String SC_PIN          = "04";
98    static final String SC_PIN2         = "042";
99    static final String SC_PUK          = "05";
100    static final String SC_PUK2         = "052";
101
102    //***** Event Constants
103
104    static final int EVENT_SET_COMPLETE         = 1;
105    static final int EVENT_GET_CLIR_COMPLETE    = 2;
106    static final int EVENT_QUERY_CF_COMPLETE    = 3;
107    static final int EVENT_USSD_COMPLETE        = 4;
108    static final int EVENT_QUERY_COMPLETE       = 5;
109    static final int EVENT_SET_CFF_COMPLETE     = 6;
110    static final int EVENT_USSD_CANCEL_COMPLETE = 7;
111
112    //***** Instance Variables
113
114    GSMPhone phone;
115    Context context;
116    UiccCardApplication mUiccApplication;
117    IccRecords mIccRecords;
118
119    String action;              // One of ACTION_*
120    String sc;                  // Service Code
121    String sia, sib, sic;       // Service Info a,b,c
122    String poundString;         // Entire MMI string up to and including #
123    String dialingNumber;
124    String pwd;                 // For password registration
125
126    /** Set to true in processCode, not at newFromDialString time */
127    private boolean isPendingUSSD;
128
129    private boolean isUssdRequest;
130
131    State state = State.PENDING;
132    CharSequence message;
133
134    //***** Class Variables
135
136
137    // See TS 22.030 6.5.2 "Structure of the MMI"
138
139    static Pattern sPatternSuppService = Pattern.compile(
140        "((\\*|#|\\*#|\\*\\*|##)(\\d{2,3})(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*))?)?)?)?#)(.*)");
141/*       1  2                    3          4  5       6   7         8    9     10  11             12
142
143         1 = Full string up to and including #
144         2 = action (activation/interrogation/registration/erasure)
145         3 = service code
146         5 = SIA
147         7 = SIB
148         9 = SIC
149         10 = dialing number
150*/
151
152    static final int MATCH_GROUP_POUND_STRING = 1;
153
154    static final int MATCH_GROUP_ACTION = 2;
155                        //(activation/interrogation/registration/erasure)
156
157    static final int MATCH_GROUP_SERVICE_CODE = 3;
158    static final int MATCH_GROUP_SIA = 5;
159    static final int MATCH_GROUP_SIB = 7;
160    static final int MATCH_GROUP_SIC = 9;
161    static final int MATCH_GROUP_PWD_CONFIRM = 11;
162    static final int MATCH_GROUP_DIALING_NUMBER = 12;
163    static private String[] sTwoDigitNumberPattern;
164
165    //***** Public Class methods
166
167    /**
168     * Some dial strings in GSM are defined to do non-call setup
169     * things, such as modify or query supplementary service settings (eg, call
170     * forwarding). These are generally referred to as "MMI codes".
171     * We look to see if the dial string contains a valid MMI code (potentially
172     * with a dial string at the end as well) and return info here.
173     *
174     * If the dial string contains no MMI code, we return an instance with
175     * only "dialingNumber" set
176     *
177     * Please see flow chart in TS 22.030 6.5.3.2
178     */
179
180    static GsmMmiCode
181    newFromDialString(String dialString, GSMPhone phone, UiccCardApplication app) {
182        Matcher m;
183        GsmMmiCode ret = null;
184
185        m = sPatternSuppService.matcher(dialString);
186
187        // Is this formatted like a standard supplementary service code?
188        if (m.matches()) {
189            ret = new GsmMmiCode(phone, app);
190            ret.poundString = makeEmptyNull(m.group(MATCH_GROUP_POUND_STRING));
191            ret.action = makeEmptyNull(m.group(MATCH_GROUP_ACTION));
192            ret.sc = makeEmptyNull(m.group(MATCH_GROUP_SERVICE_CODE));
193            ret.sia = makeEmptyNull(m.group(MATCH_GROUP_SIA));
194            ret.sib = makeEmptyNull(m.group(MATCH_GROUP_SIB));
195            ret.sic = makeEmptyNull(m.group(MATCH_GROUP_SIC));
196            ret.pwd = makeEmptyNull(m.group(MATCH_GROUP_PWD_CONFIRM));
197            ret.dialingNumber = makeEmptyNull(m.group(MATCH_GROUP_DIALING_NUMBER));
198            // According to TS 22.030 6.5.2 "Structure of the MMI",
199            // the dialing number should not ending with #.
200            // The dialing number ending # is treated as unique USSD,
201            // eg, *400#16 digit number# to recharge the prepaid card
202            // in India operator(Mumbai MTNL)
203            if(ret.dialingNumber != null &&
204                    ret.dialingNumber.endsWith("#") &&
205                    dialString.endsWith("#")){
206                ret = new GsmMmiCode(phone, app);
207                ret.poundString = dialString;
208            }
209        } else if (dialString.endsWith("#")) {
210            // TS 22.030 sec 6.5.3.2
211            // "Entry of any characters defined in the 3GPP TS 23.038 [8] Default Alphabet
212            // (up to the maximum defined in 3GPP TS 24.080 [10]), followed by #SEND".
213
214            ret = new GsmMmiCode(phone, app);
215            ret.poundString = dialString;
216        } else if (isTwoDigitShortCode(phone.getContext(), dialString)) {
217            //Is a country-specific exception to short codes as defined in TS 22.030, 6.5.3.2
218            ret = null;
219        } else if (isShortCode(dialString, phone)) {
220            // this may be a short code, as defined in TS 22.030, 6.5.3.2
221            ret = new GsmMmiCode(phone, app);
222            ret.dialingNumber = dialString;
223        }
224
225        return ret;
226    }
227
228    static GsmMmiCode
229    newNetworkInitiatedUssd (String ussdMessage,
230                                boolean isUssdRequest, GSMPhone phone, UiccCardApplication app) {
231        GsmMmiCode ret;
232
233        ret = new GsmMmiCode(phone, app);
234
235        ret.message = ussdMessage;
236        ret.isUssdRequest = isUssdRequest;
237
238        // If it's a request, set to PENDING so that it's cancelable.
239        if (isUssdRequest) {
240            ret.isPendingUSSD = true;
241            ret.state = State.PENDING;
242        } else {
243            ret.state = State.COMPLETE;
244        }
245
246        return ret;
247    }
248
249    static GsmMmiCode newFromUssdUserInput(String ussdMessge,
250                                           GSMPhone phone,
251                                           UiccCardApplication app) {
252        GsmMmiCode ret = new GsmMmiCode(phone, app);
253
254        ret.message = ussdMessge;
255        ret.state = State.PENDING;
256        ret.isPendingUSSD = true;
257
258        return ret;
259    }
260
261    //***** Private Class methods
262
263    /** make empty strings be null.
264     *  Regexp returns empty strings for empty groups
265     */
266    private static String
267    makeEmptyNull (String s) {
268        if (s != null && s.length() == 0) return null;
269
270        return s;
271    }
272
273    /** returns true of the string is empty or null */
274    private static boolean
275    isEmptyOrNull(CharSequence s) {
276        return s == null || (s.length() == 0);
277    }
278
279
280    private static int
281    scToCallForwardReason(String sc) {
282        if (sc == null) {
283            throw new RuntimeException ("invalid call forward sc");
284        }
285
286        if (sc.equals(SC_CF_All)) {
287           return CommandsInterface.CF_REASON_ALL;
288        } else if (sc.equals(SC_CFU)) {
289            return CommandsInterface.CF_REASON_UNCONDITIONAL;
290        } else if (sc.equals(SC_CFB)) {
291            return CommandsInterface.CF_REASON_BUSY;
292        } else if (sc.equals(SC_CFNR)) {
293            return CommandsInterface.CF_REASON_NOT_REACHABLE;
294        } else if (sc.equals(SC_CFNRy)) {
295            return CommandsInterface.CF_REASON_NO_REPLY;
296        } else if (sc.equals(SC_CF_All_Conditional)) {
297           return CommandsInterface.CF_REASON_ALL_CONDITIONAL;
298        } else {
299            throw new RuntimeException ("invalid call forward sc");
300        }
301    }
302
303    private static int
304    siToServiceClass(String si) {
305        if (si == null || si.length() == 0) {
306                return  SERVICE_CLASS_NONE;
307        } else {
308            // NumberFormatException should cause MMI fail
309            int serviceCode = Integer.parseInt(si, 10);
310
311            switch (serviceCode) {
312                case 10: return SERVICE_CLASS_SMS + SERVICE_CLASS_FAX  + SERVICE_CLASS_VOICE;
313                case 11: return SERVICE_CLASS_VOICE;
314                case 12: return SERVICE_CLASS_SMS + SERVICE_CLASS_FAX;
315                case 13: return SERVICE_CLASS_FAX;
316
317                case 16: return SERVICE_CLASS_SMS;
318
319                case 19: return SERVICE_CLASS_FAX + SERVICE_CLASS_VOICE;
320/*
321    Note for code 20:
322     From TS 22.030 Annex C:
323                "All GPRS bearer services" are not included in "All tele and bearer services"
324                    and "All bearer services"."
325....so SERVICE_CLASS_DATA, which (according to 27.007) includes GPRS
326*/
327                case 20: return SERVICE_CLASS_DATA_ASYNC + SERVICE_CLASS_DATA_SYNC;
328
329                case 21: return SERVICE_CLASS_PAD + SERVICE_CLASS_DATA_ASYNC;
330                case 22: return SERVICE_CLASS_PACKET + SERVICE_CLASS_DATA_SYNC;
331                case 24: return SERVICE_CLASS_DATA_SYNC;
332                case 25: return SERVICE_CLASS_DATA_ASYNC;
333                case 26: return SERVICE_CLASS_DATA_SYNC + SERVICE_CLASS_VOICE;
334                case 99: return SERVICE_CLASS_PACKET;
335
336                default:
337                    throw new RuntimeException("unsupported MMI service code " + si);
338            }
339        }
340    }
341
342    private static int
343    siToTime (String si) {
344        if (si == null || si.length() == 0) {
345            return 0;
346        } else {
347            // NumberFormatException should cause MMI fail
348            return Integer.parseInt(si, 10);
349        }
350    }
351
352    static boolean
353    isServiceCodeCallForwarding(String sc) {
354        return sc != null &&
355                (sc.equals(SC_CFU)
356                || sc.equals(SC_CFB) || sc.equals(SC_CFNRy)
357                || sc.equals(SC_CFNR) || sc.equals(SC_CF_All)
358                || sc.equals(SC_CF_All_Conditional));
359    }
360
361    static boolean
362    isServiceCodeCallBarring(String sc) {
363        return sc != null &&
364                (sc.equals(SC_BAOC)
365                || sc.equals(SC_BAOIC)
366                || sc.equals(SC_BAOICxH)
367                || sc.equals(SC_BAIC)
368                || sc.equals(SC_BAICr)
369                || sc.equals(SC_BA_ALL)
370                || sc.equals(SC_BA_MO)
371                || sc.equals(SC_BA_MT));
372    }
373
374    static String
375    scToBarringFacility(String sc) {
376        if (sc == null) {
377            throw new RuntimeException ("invalid call barring sc");
378        }
379
380        if (sc.equals(SC_BAOC)) {
381            return CommandsInterface.CB_FACILITY_BAOC;
382        } else if (sc.equals(SC_BAOIC)) {
383            return CommandsInterface.CB_FACILITY_BAOIC;
384        } else if (sc.equals(SC_BAOICxH)) {
385            return CommandsInterface.CB_FACILITY_BAOICxH;
386        } else if (sc.equals(SC_BAIC)) {
387            return CommandsInterface.CB_FACILITY_BAIC;
388        } else if (sc.equals(SC_BAICr)) {
389            return CommandsInterface.CB_FACILITY_BAICr;
390        } else if (sc.equals(SC_BA_ALL)) {
391            return CommandsInterface.CB_FACILITY_BA_ALL;
392        } else if (sc.equals(SC_BA_MO)) {
393            return CommandsInterface.CB_FACILITY_BA_MO;
394        } else if (sc.equals(SC_BA_MT)) {
395            return CommandsInterface.CB_FACILITY_BA_MT;
396        } else {
397            throw new RuntimeException ("invalid call barring sc");
398        }
399    }
400
401    //***** Constructor
402
403    GsmMmiCode (GSMPhone phone, UiccCardApplication app) {
404        // The telephony unit-test cases may create GsmMmiCode's
405        // in secondary threads
406        super(phone.getHandler().getLooper());
407        this.phone = phone;
408        this.context = phone.getContext();
409        mUiccApplication = app;
410        if (app != null) {
411            mIccRecords = app.getIccRecords();
412        }
413    }
414
415    //***** MmiCode implementation
416
417    public State
418    getState() {
419        return state;
420    }
421
422    public CharSequence
423    getMessage() {
424        return message;
425    }
426
427    // inherited javadoc suffices
428    public void
429    cancel() {
430        // Complete or failed cannot be cancelled
431        if (state == State.COMPLETE || state == State.FAILED) {
432            return;
433        }
434
435        state = State.CANCELLED;
436
437        if (isPendingUSSD) {
438            /*
439             * There can only be one pending USSD session, so tell the radio to
440             * cancel it.
441             */
442            phone.mCM.cancelPendingUssd(obtainMessage(EVENT_USSD_CANCEL_COMPLETE, this));
443
444            /*
445             * Don't call phone.onMMIDone here; wait for CANCEL_COMPLETE notice
446             * from RIL.
447             */
448        } else {
449            // TODO in cases other than USSD, it would be nice to cancel
450            // the pending radio operation. This requires RIL cancellation
451            // support, which does not presently exist.
452
453            phone.onMMIDone (this);
454        }
455
456    }
457
458    public boolean isCancelable() {
459        /* Can only cancel pending USSD sessions. */
460        return isPendingUSSD;
461    }
462
463    //***** Instance Methods
464
465    /** Does this dial string contain a structured or unstructured MMI code? */
466    boolean
467    isMMI() {
468        return poundString != null;
469    }
470
471    /* Is this a 1 or 2 digit "short code" as defined in TS 22.030 sec 6.5.3.2? */
472    boolean
473    isShortCode() {
474        return poundString == null
475                    && dialingNumber != null && dialingNumber.length() <= 2;
476
477    }
478
479    static private boolean
480    isTwoDigitShortCode(Context context, String dialString) {
481        Rlog.d(LOG_TAG, "isTwoDigitShortCode");
482
483        if (dialString == null || dialString.length() != 2) return false;
484
485        if (sTwoDigitNumberPattern == null) {
486            sTwoDigitNumberPattern = context.getResources().getStringArray(
487                    com.android.internal.R.array.config_twoDigitNumberPattern);
488        }
489
490        for (String dialnumber : sTwoDigitNumberPattern) {
491            Rlog.d(LOG_TAG, "Two Digit Number Pattern " + dialnumber);
492            if (dialString.equals(dialnumber)) {
493                Rlog.d(LOG_TAG, "Two Digit Number Pattern -true");
494                return true;
495            }
496        }
497        Rlog.d(LOG_TAG, "Two Digit Number Pattern -false");
498        return false;
499    }
500
501    /**
502     * Helper function for newFromDialString. Returns true if dialString appears
503     * to be a short code AND conditions are correct for it to be treated as
504     * such.
505     */
506    static private boolean isShortCode(String dialString, GSMPhone phone) {
507        // Refer to TS 22.030 Figure 3.5.3.2:
508        if (dialString == null) {
509            return false;
510        }
511
512        // Illegal dial string characters will give a ZERO length.
513        // At this point we do not want to crash as any application with
514        // call privileges may send a non dial string.
515        // It return false as when the dialString is equal to NULL.
516        if (dialString.length() == 0) {
517            return false;
518        }
519
520        if (PhoneNumberUtils.isLocalEmergencyNumber(dialString, phone.getContext())) {
521            return false;
522        } else {
523            return isShortCodeUSSD(dialString, phone);
524        }
525    }
526
527    /**
528     * Helper function for isShortCode. Returns true if dialString appears to be
529     * a short code and it is a USSD structure
530     *
531     * According to the 3PGG TS 22.030 specification Figure 3.5.3.2: A 1 or 2
532     * digit "short code" is treated as USSD if it is entered while on a call or
533     * does not satisfy the condition (exactly 2 digits && starts with '1'), there
534     * are however exceptions to this rule (see below)
535     *
536     * Exception (1) to Call initiation is: If the user of the device is already in a call
537     * and enters a Short String without any #-key at the end and the length of the Short String is
538     * equal or less then the MAX_LENGTH_SHORT_CODE [constant that is equal to 2]
539     *
540     * The phone shall initiate a USSD/SS commands.
541     *
542     * Exception (2) to Call initiation is: If the user of the device enters one
543     * Digit followed by the #-key. This rule defines this String as the
544     * #-String which is a USSD/SS command.
545     *
546     * The phone shall initiate a USSD/SS command.
547     */
548    static private boolean isShortCodeUSSD(String dialString, GSMPhone phone) {
549        if (dialString != null) {
550            if (phone.isInCall()) {
551                // The maximum length of a Short Code (aka Short String) is 2
552                if (dialString.length() <= MAX_LENGTH_SHORT_CODE) {
553                    return true;
554                }
555            }
556
557            // The maximum length of a Short Code (aka Short String) is 2
558            if (dialString.length() <= MAX_LENGTH_SHORT_CODE) {
559                if (dialString.charAt(dialString.length() - 1) == END_OF_USSD_COMMAND) {
560                    return true;
561                }
562            }
563        }
564        return false;
565    }
566
567    /**
568     * @return true if the Service Code is PIN/PIN2/PUK/PUK2-related
569     */
570    boolean isPinCommand() {
571        return sc != null && (sc.equals(SC_PIN) || sc.equals(SC_PIN2)
572                              || sc.equals(SC_PUK) || sc.equals(SC_PUK2));
573     }
574
575    /**
576     * See TS 22.030 Annex B.
577     * In temporary mode, to suppress CLIR for a single call, enter:
578     *      " * 31 # [called number] SEND "
579     *  In temporary mode, to invoke CLIR for a single call enter:
580     *       " # 31 # [called number] SEND "
581     */
582    boolean
583    isTemporaryModeCLIR() {
584        return sc != null && sc.equals(SC_CLIR) && dialingNumber != null
585                && (isActivate() || isDeactivate());
586    }
587
588    /**
589     * returns CommandsInterface.CLIR_*
590     * See also isTemporaryModeCLIR()
591     */
592    int
593    getCLIRMode() {
594        if (sc != null && sc.equals(SC_CLIR)) {
595            if (isActivate()) {
596                return CommandsInterface.CLIR_SUPPRESSION;
597            } else if (isDeactivate()) {
598                return CommandsInterface.CLIR_INVOCATION;
599            }
600        }
601
602        return CommandsInterface.CLIR_DEFAULT;
603    }
604
605    boolean isActivate() {
606        return action != null && action.equals(ACTION_ACTIVATE);
607    }
608
609    boolean isDeactivate() {
610        return action != null && action.equals(ACTION_DEACTIVATE);
611    }
612
613    boolean isInterrogate() {
614        return action != null && action.equals(ACTION_INTERROGATE);
615    }
616
617    boolean isRegister() {
618        return action != null && action.equals(ACTION_REGISTER);
619    }
620
621    boolean isErasure() {
622        return action != null && action.equals(ACTION_ERASURE);
623    }
624
625    /**
626     * Returns true if this is a USSD code that's been submitted to the
627     * network...eg, after processCode() is called
628     */
629    public boolean isPendingUSSD() {
630        return isPendingUSSD;
631    }
632
633    public boolean isUssdRequest() {
634        return isUssdRequest;
635    }
636
637    /** Process a MMI code or short code...anything that isn't a dialing number */
638    void
639    processCode () {
640        try {
641            if (isShortCode()) {
642                Rlog.d(LOG_TAG, "isShortCode");
643                // These just get treated as USSD.
644                sendUssd(dialingNumber);
645            } else if (dialingNumber != null) {
646                // We should have no dialing numbers here
647                throw new RuntimeException ("Invalid or Unsupported MMI Code");
648            } else if (sc != null && sc.equals(SC_CLIP)) {
649                Rlog.d(LOG_TAG, "is CLIP");
650                if (isInterrogate()) {
651                    phone.mCM.queryCLIP(
652                            obtainMessage(EVENT_QUERY_COMPLETE, this));
653                } else {
654                    throw new RuntimeException ("Invalid or Unsupported MMI Code");
655                }
656            } else if (sc != null && sc.equals(SC_CLIR)) {
657                Rlog.d(LOG_TAG, "is CLIR");
658                if (isActivate()) {
659                    phone.mCM.setCLIR(CommandsInterface.CLIR_INVOCATION,
660                        obtainMessage(EVENT_SET_COMPLETE, this));
661                } else if (isDeactivate()) {
662                    phone.mCM.setCLIR(CommandsInterface.CLIR_SUPPRESSION,
663                        obtainMessage(EVENT_SET_COMPLETE, this));
664                } else if (isInterrogate()) {
665                    phone.mCM.getCLIR(
666                        obtainMessage(EVENT_GET_CLIR_COMPLETE, this));
667                } else {
668                    throw new RuntimeException ("Invalid or Unsupported MMI Code");
669                }
670            } else if (isServiceCodeCallForwarding(sc)) {
671                Rlog.d(LOG_TAG, "is CF");
672
673                String dialingNumber = sia;
674                int serviceClass = siToServiceClass(sib);
675                int reason = scToCallForwardReason(sc);
676                int time = siToTime(sic);
677
678                if (isInterrogate()) {
679                    phone.mCM.queryCallForwardStatus(
680                            reason, serviceClass,  dialingNumber,
681                                obtainMessage(EVENT_QUERY_CF_COMPLETE, this));
682                } else {
683                    int cfAction;
684
685                    if (isActivate()) {
686                        cfAction = CommandsInterface.CF_ACTION_ENABLE;
687                    } else if (isDeactivate()) {
688                        cfAction = CommandsInterface.CF_ACTION_DISABLE;
689                    } else if (isRegister()) {
690                        cfAction = CommandsInterface.CF_ACTION_REGISTRATION;
691                    } else if (isErasure()) {
692                        cfAction = CommandsInterface.CF_ACTION_ERASURE;
693                    } else {
694                        throw new RuntimeException ("invalid action");
695                    }
696
697                    int isSettingUnconditionalVoice =
698                        (((reason == CommandsInterface.CF_REASON_UNCONDITIONAL) ||
699                                (reason == CommandsInterface.CF_REASON_ALL)) &&
700                                (((serviceClass & CommandsInterface.SERVICE_CLASS_VOICE) != 0) ||
701                                 (serviceClass == CommandsInterface.SERVICE_CLASS_NONE))) ? 1 : 0;
702
703                    int isEnableDesired =
704                        ((cfAction == CommandsInterface.CF_ACTION_ENABLE) ||
705                                (cfAction == CommandsInterface.CF_ACTION_REGISTRATION)) ? 1 : 0;
706
707                    Rlog.d(LOG_TAG, "is CF setCallForward");
708                    phone.mCM.setCallForward(cfAction, reason, serviceClass,
709                            dialingNumber, time, obtainMessage(
710                                    EVENT_SET_CFF_COMPLETE,
711                                    isSettingUnconditionalVoice,
712                                    isEnableDesired, this));
713                }
714            } else if (isServiceCodeCallBarring(sc)) {
715                // sia = password
716                // sib = basic service group
717
718                String password = sia;
719                int serviceClass = siToServiceClass(sib);
720                String facility = scToBarringFacility(sc);
721
722                if (isInterrogate()) {
723                    phone.mCM.queryFacilityLock(facility, password,
724                            serviceClass, obtainMessage(EVENT_QUERY_COMPLETE, this));
725                } else if (isActivate() || isDeactivate()) {
726                    phone.mCM.setFacilityLock(facility, isActivate(), password,
727                            serviceClass, obtainMessage(EVENT_SET_COMPLETE, this));
728                } else {
729                    throw new RuntimeException ("Invalid or Unsupported MMI Code");
730                }
731
732            } else if (sc != null && sc.equals(SC_PWD)) {
733                // sia = fac
734                // sib = old pwd
735                // sic = new pwd
736                // pwd = new pwd
737                String facility;
738                String oldPwd = sib;
739                String newPwd = sic;
740                if (isActivate() || isRegister()) {
741                    // Even though ACTIVATE is acceptable, this is really termed a REGISTER
742                    action = ACTION_REGISTER;
743
744                    if (sia == null) {
745                        // If sc was not specified, treat it as BA_ALL.
746                        facility = CommandsInterface.CB_FACILITY_BA_ALL;
747                    } else {
748                        facility = scToBarringFacility(sia);
749                    }
750                    if (newPwd.equals(pwd)) {
751                        phone.mCM.changeBarringPassword(facility, oldPwd,
752                                newPwd, obtainMessage(EVENT_SET_COMPLETE, this));
753                    } else {
754                        // password mismatch; return error
755                        handlePasswordError(com.android.internal.R.string.passwordIncorrect);
756                    }
757                } else {
758                    throw new RuntimeException ("Invalid or Unsupported MMI Code");
759                }
760
761            } else if (sc != null && sc.equals(SC_WAIT)) {
762                // sia = basic service group
763                int serviceClass = siToServiceClass(sia);
764
765                if (isActivate() || isDeactivate()) {
766                    phone.mCM.setCallWaiting(isActivate(), serviceClass,
767                            obtainMessage(EVENT_SET_COMPLETE, this));
768                } else if (isInterrogate()) {
769                    phone.mCM.queryCallWaiting(serviceClass,
770                            obtainMessage(EVENT_QUERY_COMPLETE, this));
771                } else {
772                    throw new RuntimeException ("Invalid or Unsupported MMI Code");
773                }
774            } else if (isPinCommand()) {
775                // sia = old PIN or PUK
776                // sib = new PIN
777                // sic = new PIN
778                String oldPinOrPuk = sia;
779                String newPin = sib;
780                int pinLen = newPin.length();
781                if (isRegister()) {
782                    if (!newPin.equals(sic)) {
783                        // password mismatch; return error
784                        handlePasswordError(com.android.internal.R.string.mismatchPin);
785                    } else if (pinLen < 4 || pinLen > 8 ) {
786                        // invalid length
787                        handlePasswordError(com.android.internal.R.string.invalidPin);
788                    } else if (sc.equals(SC_PIN) &&
789                               mUiccApplication != null &&
790                               mUiccApplication.getState() == AppState.APPSTATE_PUK ) {
791                        // Sim is puk-locked
792                        handlePasswordError(com.android.internal.R.string.needPuk);
793                    } else {
794                        // pre-checks OK
795                        if (sc.equals(SC_PIN)) {
796                            phone.mCM.changeIccPin(oldPinOrPuk, newPin,
797                                    obtainMessage(EVENT_SET_COMPLETE, this));
798                        } else if (sc.equals(SC_PIN2)) {
799                            phone.mCM.changeIccPin2(oldPinOrPuk, newPin,
800                                    obtainMessage(EVENT_SET_COMPLETE, this));
801                        } else if (sc.equals(SC_PUK)) {
802                            phone.mCM.supplyIccPuk(oldPinOrPuk, newPin,
803                                    obtainMessage(EVENT_SET_COMPLETE, this));
804                        } else if (sc.equals(SC_PUK2)) {
805                            phone.mCM.supplyIccPuk2(oldPinOrPuk, newPin,
806                                    obtainMessage(EVENT_SET_COMPLETE, this));
807                        }
808                    }
809                } else {
810                    throw new RuntimeException ("Invalid or Unsupported MMI Code");
811                }
812            } else if (poundString != null) {
813                sendUssd(poundString);
814            } else {
815                throw new RuntimeException ("Invalid or Unsupported MMI Code");
816            }
817        } catch (RuntimeException exc) {
818            state = State.FAILED;
819            message = context.getText(com.android.internal.R.string.mmiError);
820            phone.onMMIDone(this);
821        }
822    }
823
824    private void handlePasswordError(int res) {
825        state = State.FAILED;
826        StringBuilder sb = new StringBuilder(getScString());
827        sb.append("\n");
828        sb.append(context.getText(res));
829        message = sb;
830        phone.onMMIDone(this);
831    }
832
833    /**
834     * Called from GSMPhone
835     *
836     * An unsolicited USSD NOTIFY or REQUEST has come in matching
837     * up with this pending USSD request
838     *
839     * Note: If REQUEST, this exchange is complete, but the session remains
840     *       active (ie, the network expects user input).
841     */
842    void
843    onUssdFinished(String ussdMessage, boolean isUssdRequest) {
844        if (state == State.PENDING) {
845            if (ussdMessage == null) {
846                message = context.getText(com.android.internal.R.string.mmiComplete);
847            } else {
848                message = ussdMessage;
849            }
850            this.isUssdRequest = isUssdRequest;
851            // If it's a request, leave it PENDING so that it's cancelable.
852            if (!isUssdRequest) {
853                state = State.COMPLETE;
854            }
855
856            phone.onMMIDone(this);
857        }
858    }
859
860    /**
861     * Called from GSMPhone
862     *
863     * The radio has reset, and this is still pending
864     */
865
866    void
867    onUssdFinishedError() {
868        if (state == State.PENDING) {
869            state = State.FAILED;
870            message = context.getText(com.android.internal.R.string.mmiError);
871
872            phone.onMMIDone(this);
873        }
874    }
875
876    void sendUssd(String ussdMessage) {
877        // Treat this as a USSD string
878        isPendingUSSD = true;
879
880        // Note that unlike most everything else, the USSD complete
881        // response does not complete this MMI code...we wait for
882        // an unsolicited USSD "Notify" or "Request".
883        // The matching up of this is done in GSMPhone.
884
885        phone.mCM.sendUSSD(ussdMessage,
886            obtainMessage(EVENT_USSD_COMPLETE, this));
887    }
888
889    /** Called from GSMPhone.handleMessage; not a Handler subclass */
890    public void
891    handleMessage (Message msg) {
892        AsyncResult ar;
893
894        switch (msg.what) {
895            case EVENT_SET_COMPLETE:
896                ar = (AsyncResult) (msg.obj);
897
898                onSetComplete(ar);
899                break;
900
901            case EVENT_SET_CFF_COMPLETE:
902                ar = (AsyncResult) (msg.obj);
903
904                /*
905                * msg.arg1 = 1 means to set unconditional voice call forwarding
906                * msg.arg2 = 1 means to enable voice call forwarding
907                */
908                if ((ar.exception == null) && (msg.arg1 == 1)) {
909                    boolean cffEnabled = (msg.arg2 == 1);
910                    if (mIccRecords != null) {
911                        mIccRecords.setVoiceCallForwardingFlag(1, cffEnabled);
912                    }
913                }
914
915                onSetComplete(ar);
916                break;
917
918            case EVENT_GET_CLIR_COMPLETE:
919                ar = (AsyncResult) (msg.obj);
920                onGetClirComplete(ar);
921            break;
922
923            case EVENT_QUERY_CF_COMPLETE:
924                ar = (AsyncResult) (msg.obj);
925                onQueryCfComplete(ar);
926            break;
927
928            case EVENT_QUERY_COMPLETE:
929                ar = (AsyncResult) (msg.obj);
930                onQueryComplete(ar);
931            break;
932
933            case EVENT_USSD_COMPLETE:
934                ar = (AsyncResult) (msg.obj);
935
936                if (ar.exception != null) {
937                    state = State.FAILED;
938                    message = getErrorMessage(ar);
939
940                    phone.onMMIDone(this);
941                }
942
943                // Note that unlike most everything else, the USSD complete
944                // response does not complete this MMI code...we wait for
945                // an unsolicited USSD "Notify" or "Request".
946                // The matching up of this is done in GSMPhone.
947
948            break;
949
950            case EVENT_USSD_CANCEL_COMPLETE:
951                phone.onMMIDone(this);
952            break;
953        }
954    }
955    //***** Private instance methods
956
957    private CharSequence getErrorMessage(AsyncResult ar) {
958
959        if (ar.exception instanceof CommandException) {
960            CommandException.Error err = ((CommandException)(ar.exception)).getCommandError();
961            if (err == CommandException.Error.FDN_CHECK_FAILURE) {
962                Rlog.i(LOG_TAG, "FDN_CHECK_FAILURE");
963                return context.getText(com.android.internal.R.string.mmiFdnError);
964            }
965        }
966
967        return context.getText(com.android.internal.R.string.mmiError);
968    }
969
970    private CharSequence getScString() {
971        if (sc != null) {
972            if (isServiceCodeCallBarring(sc)) {
973                return context.getText(com.android.internal.R.string.BaMmi);
974            } else if (isServiceCodeCallForwarding(sc)) {
975                return context.getText(com.android.internal.R.string.CfMmi);
976            } else if (sc.equals(SC_CLIP)) {
977                return context.getText(com.android.internal.R.string.ClipMmi);
978            } else if (sc.equals(SC_CLIR)) {
979                return context.getText(com.android.internal.R.string.ClirMmi);
980            } else if (sc.equals(SC_PWD)) {
981                return context.getText(com.android.internal.R.string.PwdMmi);
982            } else if (sc.equals(SC_WAIT)) {
983                return context.getText(com.android.internal.R.string.CwMmi);
984            } else if (isPinCommand()) {
985                return context.getText(com.android.internal.R.string.PinMmi);
986            }
987        }
988
989        return "";
990    }
991
992    private void
993    onSetComplete(AsyncResult ar){
994        StringBuilder sb = new StringBuilder(getScString());
995        sb.append("\n");
996
997        if (ar.exception != null) {
998            state = State.FAILED;
999            if (ar.exception instanceof CommandException) {
1000                CommandException.Error err = ((CommandException)(ar.exception)).getCommandError();
1001                if (err == CommandException.Error.PASSWORD_INCORRECT) {
1002                    if (isPinCommand()) {
1003                        // look specifically for the PUK commands and adjust
1004                        // the message accordingly.
1005                        if (sc.equals(SC_PUK) || sc.equals(SC_PUK2)) {
1006                            sb.append(context.getText(
1007                                    com.android.internal.R.string.badPuk));
1008                        } else {
1009                            sb.append(context.getText(
1010                                    com.android.internal.R.string.badPin));
1011                        }
1012                    } else {
1013                        sb.append(context.getText(
1014                                com.android.internal.R.string.passwordIncorrect));
1015                    }
1016                } else if (err == CommandException.Error.SIM_PUK2) {
1017                    sb.append(context.getText(
1018                            com.android.internal.R.string.badPin));
1019                    sb.append("\n");
1020                    sb.append(context.getText(
1021                            com.android.internal.R.string.needPuk2));
1022                } else if (err == CommandException.Error.FDN_CHECK_FAILURE) {
1023                    Rlog.i(LOG_TAG, "FDN_CHECK_FAILURE");
1024                    sb.append(context.getText(com.android.internal.R.string.mmiFdnError));
1025                } else {
1026                    sb.append(context.getText(
1027                            com.android.internal.R.string.mmiError));
1028                }
1029            } else {
1030                sb.append(context.getText(
1031                        com.android.internal.R.string.mmiError));
1032            }
1033        } else if (isActivate()) {
1034            state = State.COMPLETE;
1035            sb.append(context.getText(
1036                    com.android.internal.R.string.serviceEnabled));
1037            // Record CLIR setting
1038            if (sc.equals(SC_CLIR)) {
1039                phone.saveClirSetting(CommandsInterface.CLIR_INVOCATION);
1040            }
1041        } else if (isDeactivate()) {
1042            state = State.COMPLETE;
1043            sb.append(context.getText(
1044                    com.android.internal.R.string.serviceDisabled));
1045            // Record CLIR setting
1046            if (sc.equals(SC_CLIR)) {
1047                phone.saveClirSetting(CommandsInterface.CLIR_SUPPRESSION);
1048            }
1049        } else if (isRegister()) {
1050            state = State.COMPLETE;
1051            sb.append(context.getText(
1052                    com.android.internal.R.string.serviceRegistered));
1053        } else if (isErasure()) {
1054            state = State.COMPLETE;
1055            sb.append(context.getText(
1056                    com.android.internal.R.string.serviceErased));
1057        } else {
1058            state = State.FAILED;
1059            sb.append(context.getText(
1060                    com.android.internal.R.string.mmiError));
1061        }
1062
1063        message = sb;
1064        phone.onMMIDone(this);
1065    }
1066
1067    private void
1068    onGetClirComplete(AsyncResult ar) {
1069        StringBuilder sb = new StringBuilder(getScString());
1070        sb.append("\n");
1071
1072        if (ar.exception != null) {
1073            state = State.FAILED;
1074            sb.append(getErrorMessage(ar));
1075        } else {
1076            int clirArgs[];
1077
1078            clirArgs = (int[])ar.result;
1079
1080            // the 'm' parameter from TS 27.007 7.7
1081            switch (clirArgs[1]) {
1082                case 0: // CLIR not provisioned
1083                    sb.append(context.getText(
1084                                com.android.internal.R.string.serviceNotProvisioned));
1085                    state = State.COMPLETE;
1086                break;
1087
1088                case 1: // CLIR provisioned in permanent mode
1089                    sb.append(context.getText(
1090                                com.android.internal.R.string.CLIRPermanent));
1091                    state = State.COMPLETE;
1092                break;
1093
1094                case 2: // unknown (e.g. no network, etc.)
1095                    sb.append(context.getText(
1096                                com.android.internal.R.string.mmiError));
1097                    state = State.FAILED;
1098                break;
1099
1100                case 3: // CLIR temporary mode presentation restricted
1101
1102                    // the 'n' parameter from TS 27.007 7.7
1103                    switch (clirArgs[0]) {
1104                        default:
1105                        case 0: // Default
1106                            sb.append(context.getText(
1107                                    com.android.internal.R.string.CLIRDefaultOnNextCallOn));
1108                        break;
1109                        case 1: // CLIR invocation
1110                            sb.append(context.getText(
1111                                    com.android.internal.R.string.CLIRDefaultOnNextCallOn));
1112                        break;
1113                        case 2: // CLIR suppression
1114                            sb.append(context.getText(
1115                                    com.android.internal.R.string.CLIRDefaultOnNextCallOff));
1116                        break;
1117                    }
1118                    state = State.COMPLETE;
1119                break;
1120
1121                case 4: // CLIR temporary mode presentation allowed
1122                    // the 'n' parameter from TS 27.007 7.7
1123                    switch (clirArgs[0]) {
1124                        default:
1125                        case 0: // Default
1126                            sb.append(context.getText(
1127                                    com.android.internal.R.string.CLIRDefaultOffNextCallOff));
1128                        break;
1129                        case 1: // CLIR invocation
1130                            sb.append(context.getText(
1131                                    com.android.internal.R.string.CLIRDefaultOffNextCallOn));
1132                        break;
1133                        case 2: // CLIR suppression
1134                            sb.append(context.getText(
1135                                    com.android.internal.R.string.CLIRDefaultOffNextCallOff));
1136                        break;
1137                    }
1138
1139                    state = State.COMPLETE;
1140                break;
1141            }
1142        }
1143
1144        message = sb;
1145        phone.onMMIDone(this);
1146    }
1147
1148    /**
1149     * @param serviceClass 1 bit of the service class bit vectory
1150     * @return String to be used for call forward query MMI response text.
1151     *        Returns null if unrecognized
1152     */
1153
1154    private CharSequence
1155    serviceClassToCFString (int serviceClass) {
1156        switch (serviceClass) {
1157            case SERVICE_CLASS_VOICE:
1158                return context.getText(com.android.internal.R.string.serviceClassVoice);
1159            case SERVICE_CLASS_DATA:
1160                return context.getText(com.android.internal.R.string.serviceClassData);
1161            case SERVICE_CLASS_FAX:
1162                return context.getText(com.android.internal.R.string.serviceClassFAX);
1163            case SERVICE_CLASS_SMS:
1164                return context.getText(com.android.internal.R.string.serviceClassSMS);
1165            case SERVICE_CLASS_DATA_SYNC:
1166                return context.getText(com.android.internal.R.string.serviceClassDataSync);
1167            case SERVICE_CLASS_DATA_ASYNC:
1168                return context.getText(com.android.internal.R.string.serviceClassDataAsync);
1169            case SERVICE_CLASS_PACKET:
1170                return context.getText(com.android.internal.R.string.serviceClassPacket);
1171            case SERVICE_CLASS_PAD:
1172                return context.getText(com.android.internal.R.string.serviceClassPAD);
1173            default:
1174                return null;
1175        }
1176    }
1177
1178
1179    /** one CallForwardInfo + serviceClassMask -> one line of text */
1180    private CharSequence
1181    makeCFQueryResultMessage(CallForwardInfo info, int serviceClassMask) {
1182        CharSequence template;
1183        String sources[] = {"{0}", "{1}", "{2}"};
1184        CharSequence destinations[] = new CharSequence[3];
1185        boolean needTimeTemplate;
1186
1187        // CF_REASON_NO_REPLY also has a time value associated with
1188        // it. All others don't.
1189
1190        needTimeTemplate =
1191            (info.reason == CommandsInterface.CF_REASON_NO_REPLY);
1192
1193        if (info.status == 1) {
1194            if (needTimeTemplate) {
1195                template = context.getText(
1196                        com.android.internal.R.string.cfTemplateForwardedTime);
1197            } else {
1198                template = context.getText(
1199                        com.android.internal.R.string.cfTemplateForwarded);
1200            }
1201        } else if (info.status == 0 && isEmptyOrNull(info.number)) {
1202            template = context.getText(
1203                        com.android.internal.R.string.cfTemplateNotForwarded);
1204        } else { /* (info.status == 0) && !isEmptyOrNull(info.number) */
1205            // A call forward record that is not active but contains
1206            // a phone number is considered "registered"
1207
1208            if (needTimeTemplate) {
1209                template = context.getText(
1210                        com.android.internal.R.string.cfTemplateRegisteredTime);
1211            } else {
1212                template = context.getText(
1213                        com.android.internal.R.string.cfTemplateRegistered);
1214            }
1215        }
1216
1217        // In the template (from strings.xmls)
1218        //         {0} is one of "bearerServiceCode*"
1219        //        {1} is dialing number
1220        //      {2} is time in seconds
1221
1222        destinations[0] = serviceClassToCFString(info.serviceClass & serviceClassMask);
1223        destinations[1] = PhoneNumberUtils.stringFromStringAndTOA(info.number, info.toa);
1224        destinations[2] = Integer.toString(info.timeSeconds);
1225
1226        if (info.reason == CommandsInterface.CF_REASON_UNCONDITIONAL &&
1227                (info.serviceClass & serviceClassMask)
1228                        == CommandsInterface.SERVICE_CLASS_VOICE) {
1229            boolean cffEnabled = (info.status == 1);
1230            if (mIccRecords != null) {
1231                mIccRecords.setVoiceCallForwardingFlag(1, cffEnabled);
1232            }
1233        }
1234
1235        return TextUtils.replace(template, sources, destinations);
1236    }
1237
1238
1239    private void
1240    onQueryCfComplete(AsyncResult ar) {
1241        StringBuilder sb = new StringBuilder(getScString());
1242        sb.append("\n");
1243
1244        if (ar.exception != null) {
1245            state = State.FAILED;
1246            sb.append(getErrorMessage(ar));
1247        } else {
1248            CallForwardInfo infos[];
1249
1250            infos = (CallForwardInfo[]) ar.result;
1251
1252            if (infos.length == 0) {
1253                // Assume the default is not active
1254                sb.append(context.getText(com.android.internal.R.string.serviceDisabled));
1255
1256                // Set unconditional CFF in SIM to false
1257                if (mIccRecords != null) {
1258                    mIccRecords.setVoiceCallForwardingFlag(1, false);
1259                }
1260            } else {
1261
1262                SpannableStringBuilder tb = new SpannableStringBuilder();
1263
1264                // Each bit in the service class gets its own result line
1265                // The service classes may be split up over multiple
1266                // CallForwardInfos. So, for each service class, find out
1267                // which CallForwardInfo represents it and then build
1268                // the response text based on that
1269
1270                for (int serviceClassMask = 1
1271                            ; serviceClassMask <= SERVICE_CLASS_MAX
1272                            ; serviceClassMask <<= 1
1273                ) {
1274                    for (int i = 0, s = infos.length; i < s ; i++) {
1275                        if ((serviceClassMask & infos[i].serviceClass) != 0) {
1276                            tb.append(makeCFQueryResultMessage(infos[i],
1277                                            serviceClassMask));
1278                            tb.append("\n");
1279                        }
1280                    }
1281                }
1282                sb.append(tb);
1283            }
1284
1285            state = State.COMPLETE;
1286        }
1287
1288        message = sb;
1289        phone.onMMIDone(this);
1290
1291    }
1292
1293    private void
1294    onQueryComplete(AsyncResult ar) {
1295        StringBuilder sb = new StringBuilder(getScString());
1296        sb.append("\n");
1297
1298        if (ar.exception != null) {
1299            state = State.FAILED;
1300            sb.append(getErrorMessage(ar));
1301        } else {
1302            int[] ints = (int[])ar.result;
1303
1304            if (ints.length != 0) {
1305                if (ints[0] == 0) {
1306                    sb.append(context.getText(com.android.internal.R.string.serviceDisabled));
1307                } else if (sc.equals(SC_WAIT)) {
1308                    // Call Waiting includes additional data in the response.
1309                    sb.append(createQueryCallWaitingResultMessage(ints[1]));
1310                } else if (isServiceCodeCallBarring(sc)) {
1311                    // ints[0] for Call Barring is a bit vector of services
1312                    sb.append(createQueryCallBarringResultMessage(ints[0]));
1313                } else if (ints[0] == 1) {
1314                    // for all other services, treat it as a boolean
1315                    sb.append(context.getText(com.android.internal.R.string.serviceEnabled));
1316                } else {
1317                    sb.append(context.getText(com.android.internal.R.string.mmiError));
1318                }
1319            } else {
1320                sb.append(context.getText(com.android.internal.R.string.mmiError));
1321            }
1322            state = State.COMPLETE;
1323        }
1324
1325        message = sb;
1326        phone.onMMIDone(this);
1327    }
1328
1329    private CharSequence
1330    createQueryCallWaitingResultMessage(int serviceClass) {
1331        StringBuilder sb =
1332                new StringBuilder(context.getText(com.android.internal.R.string.serviceEnabledFor));
1333
1334        for (int classMask = 1
1335                    ; classMask <= SERVICE_CLASS_MAX
1336                    ; classMask <<= 1
1337        ) {
1338            if ((classMask & serviceClass) != 0) {
1339                sb.append("\n");
1340                sb.append(serviceClassToCFString(classMask & serviceClass));
1341            }
1342        }
1343        return sb;
1344    }
1345    private CharSequence
1346    createQueryCallBarringResultMessage(int serviceClass)
1347    {
1348        StringBuilder sb = new StringBuilder(context.getText(com.android.internal.R.string.serviceEnabledFor));
1349
1350        for (int classMask = 1
1351                    ; classMask <= SERVICE_CLASS_MAX
1352                    ; classMask <<= 1
1353        ) {
1354            if ((classMask & serviceClass) != 0) {
1355                sb.append("\n");
1356                sb.append(serviceClassToCFString(classMask & serviceClass));
1357            }
1358        }
1359        return sb;
1360    }
1361
1362    /***
1363     * TODO: It would be nice to have a method here that can take in a dialstring and
1364     * figure out if there is an MMI code embedded within it.  This code would replace
1365     * some of the string parsing functionality in the Phone App's
1366     * SpecialCharSequenceMgr class.
1367     */
1368
1369    @Override
1370    public String toString() {
1371        StringBuilder sb = new StringBuilder("GsmMmiCode {");
1372
1373        sb.append("State=" + getState());
1374        if (action != null) sb.append(" action=" + action);
1375        if (sc != null) sb.append(" sc=" + sc);
1376        if (sia != null) sb.append(" sia=" + sia);
1377        if (sib != null) sb.append(" sib=" + sib);
1378        if (sic != null) sb.append(" sic=" + sic);
1379        if (poundString != null) sb.append(" poundString=" + poundString);
1380        if (dialingNumber != null) sb.append(" dialingNumber=" + dialingNumber);
1381        if (pwd != null) sb.append(" pwd=" + pwd);
1382        sb.append("}");
1383        return sb.toString();
1384    }
1385}
1386