GsmMmiCode.java revision 10e574acd0992d22abce257b420d333f5a49e71d
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    static private boolean isShortCodeUSSD(String dialString, GSMPhone phone) {
543        if (dialString != null && dialString.length() <= MAX_LENGTH_SHORT_CODE) {
544            if (phone.isInCall()) {
545                return true;
546            }
547
548            if (dialString.length() != MAX_LENGTH_SHORT_CODE ||
549                    dialString.charAt(0) != '1') {
550                return true;
551            }
552        }
553        return false;
554    }
555
556    /**
557     * @return true if the Service Code is PIN/PIN2/PUK/PUK2-related
558     */
559    boolean isPinCommand() {
560        return sc != null && (sc.equals(SC_PIN) || sc.equals(SC_PIN2)
561                              || sc.equals(SC_PUK) || sc.equals(SC_PUK2));
562     }
563
564    /**
565     * See TS 22.030 Annex B.
566     * In temporary mode, to suppress CLIR for a single call, enter:
567     *      " * 31 # [called number] SEND "
568     *  In temporary mode, to invoke CLIR for a single call enter:
569     *       " # 31 # [called number] SEND "
570     */
571    boolean
572    isTemporaryModeCLIR() {
573        return sc != null && sc.equals(SC_CLIR) && dialingNumber != null
574                && (isActivate() || isDeactivate());
575    }
576
577    /**
578     * returns CommandsInterface.CLIR_*
579     * See also isTemporaryModeCLIR()
580     */
581    int
582    getCLIRMode() {
583        if (sc != null && sc.equals(SC_CLIR)) {
584            if (isActivate()) {
585                return CommandsInterface.CLIR_SUPPRESSION;
586            } else if (isDeactivate()) {
587                return CommandsInterface.CLIR_INVOCATION;
588            }
589        }
590
591        return CommandsInterface.CLIR_DEFAULT;
592    }
593
594    boolean isActivate() {
595        return action != null && action.equals(ACTION_ACTIVATE);
596    }
597
598    boolean isDeactivate() {
599        return action != null && action.equals(ACTION_DEACTIVATE);
600    }
601
602    boolean isInterrogate() {
603        return action != null && action.equals(ACTION_INTERROGATE);
604    }
605
606    boolean isRegister() {
607        return action != null && action.equals(ACTION_REGISTER);
608    }
609
610    boolean isErasure() {
611        return action != null && action.equals(ACTION_ERASURE);
612    }
613
614    /**
615     * Returns true if this is a USSD code that's been submitted to the
616     * network...eg, after processCode() is called
617     */
618    public boolean isPendingUSSD() {
619        return isPendingUSSD;
620    }
621
622    public boolean isUssdRequest() {
623        return isUssdRequest;
624    }
625
626    /** Process a MMI code or short code...anything that isn't a dialing number */
627    void
628    processCode () {
629        try {
630            if (isShortCode()) {
631                Rlog.d(LOG_TAG, "isShortCode");
632                // These just get treated as USSD.
633                sendUssd(dialingNumber);
634            } else if (dialingNumber != null) {
635                // We should have no dialing numbers here
636                throw new RuntimeException ("Invalid or Unsupported MMI Code");
637            } else if (sc != null && sc.equals(SC_CLIP)) {
638                Rlog.d(LOG_TAG, "is CLIP");
639                if (isInterrogate()) {
640                    phone.mCM.queryCLIP(
641                            obtainMessage(EVENT_QUERY_COMPLETE, this));
642                } else {
643                    throw new RuntimeException ("Invalid or Unsupported MMI Code");
644                }
645            } else if (sc != null && sc.equals(SC_CLIR)) {
646                Rlog.d(LOG_TAG, "is CLIR");
647                if (isActivate()) {
648                    phone.mCM.setCLIR(CommandsInterface.CLIR_INVOCATION,
649                        obtainMessage(EVENT_SET_COMPLETE, this));
650                } else if (isDeactivate()) {
651                    phone.mCM.setCLIR(CommandsInterface.CLIR_SUPPRESSION,
652                        obtainMessage(EVENT_SET_COMPLETE, this));
653                } else if (isInterrogate()) {
654                    phone.mCM.getCLIR(
655                        obtainMessage(EVENT_GET_CLIR_COMPLETE, this));
656                } else {
657                    throw new RuntimeException ("Invalid or Unsupported MMI Code");
658                }
659            } else if (isServiceCodeCallForwarding(sc)) {
660                Rlog.d(LOG_TAG, "is CF");
661
662                String dialingNumber = sia;
663                int serviceClass = siToServiceClass(sib);
664                int reason = scToCallForwardReason(sc);
665                int time = siToTime(sic);
666
667                if (isInterrogate()) {
668                    phone.mCM.queryCallForwardStatus(
669                            reason, serviceClass,  dialingNumber,
670                                obtainMessage(EVENT_QUERY_CF_COMPLETE, this));
671                } else {
672                    int cfAction;
673
674                    if (isActivate()) {
675                        cfAction = CommandsInterface.CF_ACTION_ENABLE;
676                    } else if (isDeactivate()) {
677                        cfAction = CommandsInterface.CF_ACTION_DISABLE;
678                    } else if (isRegister()) {
679                        cfAction = CommandsInterface.CF_ACTION_REGISTRATION;
680                    } else if (isErasure()) {
681                        cfAction = CommandsInterface.CF_ACTION_ERASURE;
682                    } else {
683                        throw new RuntimeException ("invalid action");
684                    }
685
686                    int isSettingUnconditionalVoice =
687                        (((reason == CommandsInterface.CF_REASON_UNCONDITIONAL) ||
688                                (reason == CommandsInterface.CF_REASON_ALL)) &&
689                                (((serviceClass & CommandsInterface.SERVICE_CLASS_VOICE) != 0) ||
690                                 (serviceClass == CommandsInterface.SERVICE_CLASS_NONE))) ? 1 : 0;
691
692                    int isEnableDesired =
693                        ((cfAction == CommandsInterface.CF_ACTION_ENABLE) ||
694                                (cfAction == CommandsInterface.CF_ACTION_REGISTRATION)) ? 1 : 0;
695
696                    Rlog.d(LOG_TAG, "is CF setCallForward");
697                    phone.mCM.setCallForward(cfAction, reason, serviceClass,
698                            dialingNumber, time, obtainMessage(
699                                    EVENT_SET_CFF_COMPLETE,
700                                    isSettingUnconditionalVoice,
701                                    isEnableDesired, this));
702                }
703            } else if (isServiceCodeCallBarring(sc)) {
704                // sia = password
705                // sib = basic service group
706
707                String password = sia;
708                int serviceClass = siToServiceClass(sib);
709                String facility = scToBarringFacility(sc);
710
711                if (isInterrogate()) {
712                    phone.mCM.queryFacilityLock(facility, password,
713                            serviceClass, obtainMessage(EVENT_QUERY_COMPLETE, this));
714                } else if (isActivate() || isDeactivate()) {
715                    phone.mCM.setFacilityLock(facility, isActivate(), password,
716                            serviceClass, obtainMessage(EVENT_SET_COMPLETE, this));
717                } else {
718                    throw new RuntimeException ("Invalid or Unsupported MMI Code");
719                }
720
721            } else if (sc != null && sc.equals(SC_PWD)) {
722                // sia = fac
723                // sib = old pwd
724                // sic = new pwd
725                // pwd = new pwd
726                String facility;
727                String oldPwd = sib;
728                String newPwd = sic;
729                if (isActivate() || isRegister()) {
730                    // Even though ACTIVATE is acceptable, this is really termed a REGISTER
731                    action = ACTION_REGISTER;
732
733                    if (sia == null) {
734                        // If sc was not specified, treat it as BA_ALL.
735                        facility = CommandsInterface.CB_FACILITY_BA_ALL;
736                    } else {
737                        facility = scToBarringFacility(sia);
738                    }
739                    if (newPwd.equals(pwd)) {
740                        phone.mCM.changeBarringPassword(facility, oldPwd,
741                                newPwd, obtainMessage(EVENT_SET_COMPLETE, this));
742                    } else {
743                        // password mismatch; return error
744                        handlePasswordError(com.android.internal.R.string.passwordIncorrect);
745                    }
746                } else {
747                    throw new RuntimeException ("Invalid or Unsupported MMI Code");
748                }
749
750            } else if (sc != null && sc.equals(SC_WAIT)) {
751                // sia = basic service group
752                int serviceClass = siToServiceClass(sia);
753
754                if (isActivate() || isDeactivate()) {
755                    phone.mCM.setCallWaiting(isActivate(), serviceClass,
756                            obtainMessage(EVENT_SET_COMPLETE, this));
757                } else if (isInterrogate()) {
758                    phone.mCM.queryCallWaiting(serviceClass,
759                            obtainMessage(EVENT_QUERY_COMPLETE, this));
760                } else {
761                    throw new RuntimeException ("Invalid or Unsupported MMI Code");
762                }
763            } else if (isPinCommand()) {
764                // sia = old PIN or PUK
765                // sib = new PIN
766                // sic = new PIN
767                String oldPinOrPuk = sia;
768                String newPin = sib;
769                int pinLen = newPin.length();
770                if (isRegister()) {
771                    if (!newPin.equals(sic)) {
772                        // password mismatch; return error
773                        handlePasswordError(com.android.internal.R.string.mismatchPin);
774                    } else if (pinLen < 4 || pinLen > 8 ) {
775                        // invalid length
776                        handlePasswordError(com.android.internal.R.string.invalidPin);
777                    } else if (sc.equals(SC_PIN) &&
778                               mUiccApplication != null &&
779                               mUiccApplication.getState() == AppState.APPSTATE_PUK ) {
780                        // Sim is puk-locked
781                        handlePasswordError(com.android.internal.R.string.needPuk);
782                    } else {
783                        // pre-checks OK
784                        if (sc.equals(SC_PIN)) {
785                            phone.mCM.changeIccPin(oldPinOrPuk, newPin,
786                                    obtainMessage(EVENT_SET_COMPLETE, this));
787                        } else if (sc.equals(SC_PIN2)) {
788                            phone.mCM.changeIccPin2(oldPinOrPuk, newPin,
789                                    obtainMessage(EVENT_SET_COMPLETE, this));
790                        } else if (sc.equals(SC_PUK)) {
791                            phone.mCM.supplyIccPuk(oldPinOrPuk, newPin,
792                                    obtainMessage(EVENT_SET_COMPLETE, this));
793                        } else if (sc.equals(SC_PUK2)) {
794                            phone.mCM.supplyIccPuk2(oldPinOrPuk, newPin,
795                                    obtainMessage(EVENT_SET_COMPLETE, this));
796                        }
797                    }
798                } else {
799                    throw new RuntimeException ("Invalid or Unsupported MMI Code");
800                }
801            } else if (poundString != null) {
802                sendUssd(poundString);
803            } else {
804                throw new RuntimeException ("Invalid or Unsupported MMI Code");
805            }
806        } catch (RuntimeException exc) {
807            state = State.FAILED;
808            message = context.getText(com.android.internal.R.string.mmiError);
809            phone.onMMIDone(this);
810        }
811    }
812
813    private void handlePasswordError(int res) {
814        state = State.FAILED;
815        StringBuilder sb = new StringBuilder(getScString());
816        sb.append("\n");
817        sb.append(context.getText(res));
818        message = sb;
819        phone.onMMIDone(this);
820    }
821
822    /**
823     * Called from GSMPhone
824     *
825     * An unsolicited USSD NOTIFY or REQUEST has come in matching
826     * up with this pending USSD request
827     *
828     * Note: If REQUEST, this exchange is complete, but the session remains
829     *       active (ie, the network expects user input).
830     */
831    void
832    onUssdFinished(String ussdMessage, boolean isUssdRequest) {
833        if (state == State.PENDING) {
834            if (ussdMessage == null) {
835                message = context.getText(com.android.internal.R.string.mmiComplete);
836            } else {
837                message = ussdMessage;
838            }
839            this.isUssdRequest = isUssdRequest;
840            // If it's a request, leave it PENDING so that it's cancelable.
841            if (!isUssdRequest) {
842                state = State.COMPLETE;
843            }
844
845            phone.onMMIDone(this);
846        }
847    }
848
849    /**
850     * Called from GSMPhone
851     *
852     * The radio has reset, and this is still pending
853     */
854
855    void
856    onUssdFinishedError() {
857        if (state == State.PENDING) {
858            state = State.FAILED;
859            message = context.getText(com.android.internal.R.string.mmiError);
860
861            phone.onMMIDone(this);
862        }
863    }
864
865    void sendUssd(String ussdMessage) {
866        // Treat this as a USSD string
867        isPendingUSSD = true;
868
869        // Note that unlike most everything else, the USSD complete
870        // response does not complete this MMI code...we wait for
871        // an unsolicited USSD "Notify" or "Request".
872        // The matching up of this is done in GSMPhone.
873
874        phone.mCM.sendUSSD(ussdMessage,
875            obtainMessage(EVENT_USSD_COMPLETE, this));
876    }
877
878    /** Called from GSMPhone.handleMessage; not a Handler subclass */
879    public void
880    handleMessage (Message msg) {
881        AsyncResult ar;
882
883        switch (msg.what) {
884            case EVENT_SET_COMPLETE:
885                ar = (AsyncResult) (msg.obj);
886
887                onSetComplete(ar);
888                break;
889
890            case EVENT_SET_CFF_COMPLETE:
891                ar = (AsyncResult) (msg.obj);
892
893                /*
894                * msg.arg1 = 1 means to set unconditional voice call forwarding
895                * msg.arg2 = 1 means to enable voice call forwarding
896                */
897                if ((ar.exception == null) && (msg.arg1 == 1)) {
898                    boolean cffEnabled = (msg.arg2 == 1);
899                    if (mIccRecords != null) {
900                        mIccRecords.setVoiceCallForwardingFlag(1, cffEnabled);
901                    }
902                }
903
904                onSetComplete(ar);
905                break;
906
907            case EVENT_GET_CLIR_COMPLETE:
908                ar = (AsyncResult) (msg.obj);
909                onGetClirComplete(ar);
910            break;
911
912            case EVENT_QUERY_CF_COMPLETE:
913                ar = (AsyncResult) (msg.obj);
914                onQueryCfComplete(ar);
915            break;
916
917            case EVENT_QUERY_COMPLETE:
918                ar = (AsyncResult) (msg.obj);
919                onQueryComplete(ar);
920            break;
921
922            case EVENT_USSD_COMPLETE:
923                ar = (AsyncResult) (msg.obj);
924
925                if (ar.exception != null) {
926                    state = State.FAILED;
927                    message = getErrorMessage(ar);
928
929                    phone.onMMIDone(this);
930                }
931
932                // Note that unlike most everything else, the USSD complete
933                // response does not complete this MMI code...we wait for
934                // an unsolicited USSD "Notify" or "Request".
935                // The matching up of this is done in GSMPhone.
936
937            break;
938
939            case EVENT_USSD_CANCEL_COMPLETE:
940                phone.onMMIDone(this);
941            break;
942        }
943    }
944    //***** Private instance methods
945
946    private CharSequence getErrorMessage(AsyncResult ar) {
947
948        if (ar.exception instanceof CommandException) {
949            CommandException.Error err = ((CommandException)(ar.exception)).getCommandError();
950            if (err == CommandException.Error.FDN_CHECK_FAILURE) {
951                Rlog.i(LOG_TAG, "FDN_CHECK_FAILURE");
952                return context.getText(com.android.internal.R.string.mmiFdnError);
953            }
954        }
955
956        return context.getText(com.android.internal.R.string.mmiError);
957    }
958
959    private CharSequence getScString() {
960        if (sc != null) {
961            if (isServiceCodeCallBarring(sc)) {
962                return context.getText(com.android.internal.R.string.BaMmi);
963            } else if (isServiceCodeCallForwarding(sc)) {
964                return context.getText(com.android.internal.R.string.CfMmi);
965            } else if (sc.equals(SC_CLIP)) {
966                return context.getText(com.android.internal.R.string.ClipMmi);
967            } else if (sc.equals(SC_CLIR)) {
968                return context.getText(com.android.internal.R.string.ClirMmi);
969            } else if (sc.equals(SC_PWD)) {
970                return context.getText(com.android.internal.R.string.PwdMmi);
971            } else if (sc.equals(SC_WAIT)) {
972                return context.getText(com.android.internal.R.string.CwMmi);
973            } else if (isPinCommand()) {
974                return context.getText(com.android.internal.R.string.PinMmi);
975            }
976        }
977
978        return "";
979    }
980
981    private void
982    onSetComplete(AsyncResult ar){
983        StringBuilder sb = new StringBuilder(getScString());
984        sb.append("\n");
985
986        if (ar.exception != null) {
987            state = State.FAILED;
988            if (ar.exception instanceof CommandException) {
989                CommandException.Error err = ((CommandException)(ar.exception)).getCommandError();
990                if (err == CommandException.Error.PASSWORD_INCORRECT) {
991                    if (isPinCommand()) {
992                        // look specifically for the PUK commands and adjust
993                        // the message accordingly.
994                        if (sc.equals(SC_PUK) || sc.equals(SC_PUK2)) {
995                            sb.append(context.getText(
996                                    com.android.internal.R.string.badPuk));
997                        } else {
998                            sb.append(context.getText(
999                                    com.android.internal.R.string.badPin));
1000                        }
1001                    } else {
1002                        sb.append(context.getText(
1003                                com.android.internal.R.string.passwordIncorrect));
1004                    }
1005                } else if (err == CommandException.Error.SIM_PUK2) {
1006                    sb.append(context.getText(
1007                            com.android.internal.R.string.badPin));
1008                    sb.append("\n");
1009                    sb.append(context.getText(
1010                            com.android.internal.R.string.needPuk2));
1011                } else if (err == CommandException.Error.FDN_CHECK_FAILURE) {
1012                    Rlog.i(LOG_TAG, "FDN_CHECK_FAILURE");
1013                    sb.append(context.getText(com.android.internal.R.string.mmiFdnError));
1014                } else {
1015                    sb.append(context.getText(
1016                            com.android.internal.R.string.mmiError));
1017                }
1018            } else {
1019                sb.append(context.getText(
1020                        com.android.internal.R.string.mmiError));
1021            }
1022        } else if (isActivate()) {
1023            state = State.COMPLETE;
1024            sb.append(context.getText(
1025                    com.android.internal.R.string.serviceEnabled));
1026            // Record CLIR setting
1027            if (sc.equals(SC_CLIR)) {
1028                phone.saveClirSetting(CommandsInterface.CLIR_INVOCATION);
1029            }
1030        } else if (isDeactivate()) {
1031            state = State.COMPLETE;
1032            sb.append(context.getText(
1033                    com.android.internal.R.string.serviceDisabled));
1034            // Record CLIR setting
1035            if (sc.equals(SC_CLIR)) {
1036                phone.saveClirSetting(CommandsInterface.CLIR_SUPPRESSION);
1037            }
1038        } else if (isRegister()) {
1039            state = State.COMPLETE;
1040            sb.append(context.getText(
1041                    com.android.internal.R.string.serviceRegistered));
1042        } else if (isErasure()) {
1043            state = State.COMPLETE;
1044            sb.append(context.getText(
1045                    com.android.internal.R.string.serviceErased));
1046        } else {
1047            state = State.FAILED;
1048            sb.append(context.getText(
1049                    com.android.internal.R.string.mmiError));
1050        }
1051
1052        message = sb;
1053        phone.onMMIDone(this);
1054    }
1055
1056    private void
1057    onGetClirComplete(AsyncResult ar) {
1058        StringBuilder sb = new StringBuilder(getScString());
1059        sb.append("\n");
1060
1061        if (ar.exception != null) {
1062            state = State.FAILED;
1063            sb.append(getErrorMessage(ar));
1064        } else {
1065            int clirArgs[];
1066
1067            clirArgs = (int[])ar.result;
1068
1069            // the 'm' parameter from TS 27.007 7.7
1070            switch (clirArgs[1]) {
1071                case 0: // CLIR not provisioned
1072                    sb.append(context.getText(
1073                                com.android.internal.R.string.serviceNotProvisioned));
1074                    state = State.COMPLETE;
1075                break;
1076
1077                case 1: // CLIR provisioned in permanent mode
1078                    sb.append(context.getText(
1079                                com.android.internal.R.string.CLIRPermanent));
1080                    state = State.COMPLETE;
1081                break;
1082
1083                case 2: // unknown (e.g. no network, etc.)
1084                    sb.append(context.getText(
1085                                com.android.internal.R.string.mmiError));
1086                    state = State.FAILED;
1087                break;
1088
1089                case 3: // CLIR temporary mode presentation restricted
1090
1091                    // the 'n' parameter from TS 27.007 7.7
1092                    switch (clirArgs[0]) {
1093                        default:
1094                        case 0: // Default
1095                            sb.append(context.getText(
1096                                    com.android.internal.R.string.CLIRDefaultOnNextCallOn));
1097                        break;
1098                        case 1: // CLIR invocation
1099                            sb.append(context.getText(
1100                                    com.android.internal.R.string.CLIRDefaultOnNextCallOn));
1101                        break;
1102                        case 2: // CLIR suppression
1103                            sb.append(context.getText(
1104                                    com.android.internal.R.string.CLIRDefaultOnNextCallOff));
1105                        break;
1106                    }
1107                    state = State.COMPLETE;
1108                break;
1109
1110                case 4: // CLIR temporary mode presentation allowed
1111                    // the 'n' parameter from TS 27.007 7.7
1112                    switch (clirArgs[0]) {
1113                        default:
1114                        case 0: // Default
1115                            sb.append(context.getText(
1116                                    com.android.internal.R.string.CLIRDefaultOffNextCallOff));
1117                        break;
1118                        case 1: // CLIR invocation
1119                            sb.append(context.getText(
1120                                    com.android.internal.R.string.CLIRDefaultOffNextCallOn));
1121                        break;
1122                        case 2: // CLIR suppression
1123                            sb.append(context.getText(
1124                                    com.android.internal.R.string.CLIRDefaultOffNextCallOff));
1125                        break;
1126                    }
1127
1128                    state = State.COMPLETE;
1129                break;
1130            }
1131        }
1132
1133        message = sb;
1134        phone.onMMIDone(this);
1135    }
1136
1137    /**
1138     * @param serviceClass 1 bit of the service class bit vectory
1139     * @return String to be used for call forward query MMI response text.
1140     *        Returns null if unrecognized
1141     */
1142
1143    private CharSequence
1144    serviceClassToCFString (int serviceClass) {
1145        switch (serviceClass) {
1146            case SERVICE_CLASS_VOICE:
1147                return context.getText(com.android.internal.R.string.serviceClassVoice);
1148            case SERVICE_CLASS_DATA:
1149                return context.getText(com.android.internal.R.string.serviceClassData);
1150            case SERVICE_CLASS_FAX:
1151                return context.getText(com.android.internal.R.string.serviceClassFAX);
1152            case SERVICE_CLASS_SMS:
1153                return context.getText(com.android.internal.R.string.serviceClassSMS);
1154            case SERVICE_CLASS_DATA_SYNC:
1155                return context.getText(com.android.internal.R.string.serviceClassDataSync);
1156            case SERVICE_CLASS_DATA_ASYNC:
1157                return context.getText(com.android.internal.R.string.serviceClassDataAsync);
1158            case SERVICE_CLASS_PACKET:
1159                return context.getText(com.android.internal.R.string.serviceClassPacket);
1160            case SERVICE_CLASS_PAD:
1161                return context.getText(com.android.internal.R.string.serviceClassPAD);
1162            default:
1163                return null;
1164        }
1165    }
1166
1167
1168    /** one CallForwardInfo + serviceClassMask -> one line of text */
1169    private CharSequence
1170    makeCFQueryResultMessage(CallForwardInfo info, int serviceClassMask) {
1171        CharSequence template;
1172        String sources[] = {"{0}", "{1}", "{2}"};
1173        CharSequence destinations[] = new CharSequence[3];
1174        boolean needTimeTemplate;
1175
1176        // CF_REASON_NO_REPLY also has a time value associated with
1177        // it. All others don't.
1178
1179        needTimeTemplate =
1180            (info.reason == CommandsInterface.CF_REASON_NO_REPLY);
1181
1182        if (info.status == 1) {
1183            if (needTimeTemplate) {
1184                template = context.getText(
1185                        com.android.internal.R.string.cfTemplateForwardedTime);
1186            } else {
1187                template = context.getText(
1188                        com.android.internal.R.string.cfTemplateForwarded);
1189            }
1190        } else if (info.status == 0 && isEmptyOrNull(info.number)) {
1191            template = context.getText(
1192                        com.android.internal.R.string.cfTemplateNotForwarded);
1193        } else { /* (info.status == 0) && !isEmptyOrNull(info.number) */
1194            // A call forward record that is not active but contains
1195            // a phone number is considered "registered"
1196
1197            if (needTimeTemplate) {
1198                template = context.getText(
1199                        com.android.internal.R.string.cfTemplateRegisteredTime);
1200            } else {
1201                template = context.getText(
1202                        com.android.internal.R.string.cfTemplateRegistered);
1203            }
1204        }
1205
1206        // In the template (from strings.xmls)
1207        //         {0} is one of "bearerServiceCode*"
1208        //        {1} is dialing number
1209        //      {2} is time in seconds
1210
1211        destinations[0] = serviceClassToCFString(info.serviceClass & serviceClassMask);
1212        destinations[1] = PhoneNumberUtils.stringFromStringAndTOA(info.number, info.toa);
1213        destinations[2] = Integer.toString(info.timeSeconds);
1214
1215        if (info.reason == CommandsInterface.CF_REASON_UNCONDITIONAL &&
1216                (info.serviceClass & serviceClassMask)
1217                        == CommandsInterface.SERVICE_CLASS_VOICE) {
1218            boolean cffEnabled = (info.status == 1);
1219            if (mIccRecords != null) {
1220                mIccRecords.setVoiceCallForwardingFlag(1, cffEnabled);
1221            }
1222        }
1223
1224        return TextUtils.replace(template, sources, destinations);
1225    }
1226
1227
1228    private void
1229    onQueryCfComplete(AsyncResult ar) {
1230        StringBuilder sb = new StringBuilder(getScString());
1231        sb.append("\n");
1232
1233        if (ar.exception != null) {
1234            state = State.FAILED;
1235            sb.append(getErrorMessage(ar));
1236        } else {
1237            CallForwardInfo infos[];
1238
1239            infos = (CallForwardInfo[]) ar.result;
1240
1241            if (infos.length == 0) {
1242                // Assume the default is not active
1243                sb.append(context.getText(com.android.internal.R.string.serviceDisabled));
1244
1245                // Set unconditional CFF in SIM to false
1246                if (mIccRecords != null) {
1247                    mIccRecords.setVoiceCallForwardingFlag(1, false);
1248                }
1249            } else {
1250
1251                SpannableStringBuilder tb = new SpannableStringBuilder();
1252
1253                // Each bit in the service class gets its own result line
1254                // The service classes may be split up over multiple
1255                // CallForwardInfos. So, for each service class, find out
1256                // which CallForwardInfo represents it and then build
1257                // the response text based on that
1258
1259                for (int serviceClassMask = 1
1260                            ; serviceClassMask <= SERVICE_CLASS_MAX
1261                            ; serviceClassMask <<= 1
1262                ) {
1263                    for (int i = 0, s = infos.length; i < s ; i++) {
1264                        if ((serviceClassMask & infos[i].serviceClass) != 0) {
1265                            tb.append(makeCFQueryResultMessage(infos[i],
1266                                            serviceClassMask));
1267                            tb.append("\n");
1268                        }
1269                    }
1270                }
1271                sb.append(tb);
1272            }
1273
1274            state = State.COMPLETE;
1275        }
1276
1277        message = sb;
1278        phone.onMMIDone(this);
1279
1280    }
1281
1282    private void
1283    onQueryComplete(AsyncResult ar) {
1284        StringBuilder sb = new StringBuilder(getScString());
1285        sb.append("\n");
1286
1287        if (ar.exception != null) {
1288            state = State.FAILED;
1289            sb.append(getErrorMessage(ar));
1290        } else {
1291            int[] ints = (int[])ar.result;
1292
1293            if (ints.length != 0) {
1294                if (ints[0] == 0) {
1295                    sb.append(context.getText(com.android.internal.R.string.serviceDisabled));
1296                } else if (sc.equals(SC_WAIT)) {
1297                    // Call Waiting includes additional data in the response.
1298                    sb.append(createQueryCallWaitingResultMessage(ints[1]));
1299                } else if (isServiceCodeCallBarring(sc)) {
1300                    // ints[0] for Call Barring is a bit vector of services
1301                    sb.append(createQueryCallBarringResultMessage(ints[0]));
1302                } else if (ints[0] == 1) {
1303                    // for all other services, treat it as a boolean
1304                    sb.append(context.getText(com.android.internal.R.string.serviceEnabled));
1305                } else {
1306                    sb.append(context.getText(com.android.internal.R.string.mmiError));
1307                }
1308            } else {
1309                sb.append(context.getText(com.android.internal.R.string.mmiError));
1310            }
1311            state = State.COMPLETE;
1312        }
1313
1314        message = sb;
1315        phone.onMMIDone(this);
1316    }
1317
1318    private CharSequence
1319    createQueryCallWaitingResultMessage(int serviceClass) {
1320        StringBuilder sb =
1321                new StringBuilder(context.getText(com.android.internal.R.string.serviceEnabledFor));
1322
1323        for (int classMask = 1
1324                    ; classMask <= SERVICE_CLASS_MAX
1325                    ; classMask <<= 1
1326        ) {
1327            if ((classMask & serviceClass) != 0) {
1328                sb.append("\n");
1329                sb.append(serviceClassToCFString(classMask & serviceClass));
1330            }
1331        }
1332        return sb;
1333    }
1334    private CharSequence
1335    createQueryCallBarringResultMessage(int serviceClass)
1336    {
1337        StringBuilder sb = new StringBuilder(context.getText(com.android.internal.R.string.serviceEnabledFor));
1338
1339        for (int classMask = 1
1340                    ; classMask <= SERVICE_CLASS_MAX
1341                    ; classMask <<= 1
1342        ) {
1343            if ((classMask & serviceClass) != 0) {
1344                sb.append("\n");
1345                sb.append(serviceClassToCFString(classMask & serviceClass));
1346            }
1347        }
1348        return sb;
1349    }
1350
1351    /***
1352     * TODO: It would be nice to have a method here that can take in a dialstring and
1353     * figure out if there is an MMI code embedded within it.  This code would replace
1354     * some of the string parsing functionality in the Phone App's
1355     * SpecialCharSequenceMgr class.
1356     */
1357
1358    @Override
1359    public String toString() {
1360        StringBuilder sb = new StringBuilder("GsmMmiCode {");
1361
1362        sb.append("State=" + getState());
1363        if (action != null) sb.append(" action=" + action);
1364        if (sc != null) sb.append(" sc=" + sc);
1365        if (sia != null) sb.append(" sia=" + sia);
1366        if (sib != null) sb.append(" sib=" + sib);
1367        if (sic != null) sb.append(" sic=" + sic);
1368        if (poundString != null) sb.append(" poundString=" + poundString);
1369        if (dialingNumber != null) sb.append(" dialingNumber=" + dialingNumber);
1370        if (pwd != null) sb.append(" pwd=" + pwd);
1371        sb.append("}");
1372        return sb.toString();
1373    }
1374}
1375