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