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