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