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