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