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