GsmMmiCode.java revision a8467dd0c524787104b1ccdddc5e8af10ba729ed
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    public Phone
436    getPhone() {
437        return ((Phone) mPhone);
438    }
439
440    // inherited javadoc suffices
441    @Override
442    public void
443    cancel() {
444        // Complete or failed cannot be cancelled
445        if (mState == State.COMPLETE || mState == State.FAILED) {
446            return;
447        }
448
449        mState = State.CANCELLED;
450
451        if (mIsPendingUSSD) {
452            /*
453             * There can only be one pending USSD session, so tell the radio to
454             * cancel it.
455             */
456            mPhone.mCi.cancelPendingUssd(obtainMessage(EVENT_USSD_CANCEL_COMPLETE, this));
457
458            /*
459             * Don't call phone.onMMIDone here; wait for CANCEL_COMPLETE notice
460             * from RIL.
461             */
462        } else {
463            // TODO in cases other than USSD, it would be nice to cancel
464            // the pending radio operation. This requires RIL cancellation
465            // support, which does not presently exist.
466
467            mPhone.onMMIDone (this);
468        }
469
470    }
471
472    @Override
473    public boolean isCancelable() {
474        /* Can only cancel pending USSD sessions. */
475        return mIsPendingUSSD;
476    }
477
478    //***** Instance Methods
479
480    /** Does this dial string contain a structured or unstructured MMI code? */
481    boolean
482    isMMI() {
483        return mPoundString != null;
484    }
485
486    /* Is this a 1 or 2 digit "short code" as defined in TS 22.030 sec 6.5.3.2? */
487    boolean
488    isShortCode() {
489        return mPoundString == null
490                    && mDialingNumber != null && mDialingNumber.length() <= 2;
491
492    }
493
494    static private boolean
495    isTwoDigitShortCode(Context context, String dialString) {
496        Rlog.d(LOG_TAG, "isTwoDigitShortCode");
497
498        if (dialString == null || dialString.length() != 2) return false;
499
500        if (sTwoDigitNumberPattern == null) {
501            sTwoDigitNumberPattern = context.getResources().getStringArray(
502                    com.android.internal.R.array.config_twoDigitNumberPattern);
503        }
504
505        for (String dialnumber : sTwoDigitNumberPattern) {
506            Rlog.d(LOG_TAG, "Two Digit Number Pattern " + dialnumber);
507            if (dialString.equals(dialnumber)) {
508                Rlog.d(LOG_TAG, "Two Digit Number Pattern -true");
509                return true;
510            }
511        }
512        Rlog.d(LOG_TAG, "Two Digit Number Pattern -false");
513        return false;
514    }
515
516    /**
517     * Helper function for newFromDialString. Returns true if dialString appears
518     * to be a short code AND conditions are correct for it to be treated as
519     * such.
520     */
521    static private boolean isShortCode(String dialString, GSMPhone phone) {
522        // Refer to TS 22.030 Figure 3.5.3.2:
523        if (dialString == null) {
524            return false;
525        }
526
527        // Illegal dial string characters will give a ZERO length.
528        // At this point we do not want to crash as any application with
529        // call privileges may send a non dial string.
530        // It return false as when the dialString is equal to NULL.
531        if (dialString.length() == 0) {
532            return false;
533        }
534
535        if (PhoneNumberUtils.isLocalEmergencyNumber(phone.getContext(), dialString)) {
536            return false;
537        } else {
538            return isShortCodeUSSD(dialString, phone);
539        }
540    }
541
542    /**
543     * Helper function for isShortCode. Returns true if dialString appears to be
544     * a short code and it is a USSD structure
545     *
546     * According to the 3PGG TS 22.030 specification Figure 3.5.3.2: A 1 or 2
547     * digit "short code" is treated as USSD if it is entered while on a call or
548     * does not satisfy the condition (exactly 2 digits && starts with '1'), there
549     * are however exceptions to this rule (see below)
550     *
551     * Exception (1) to Call initiation is: If the user of the device is already in a call
552     * and enters a Short String without any #-key at the end and the length of the Short String is
553     * equal or less then the MAX_LENGTH_SHORT_CODE [constant that is equal to 2]
554     *
555     * The phone shall initiate a USSD/SS commands.
556     */
557    static private boolean isShortCodeUSSD(String dialString, GSMPhone phone) {
558        if (dialString != null && dialString.length() <= MAX_LENGTH_SHORT_CODE) {
559            if (phone.isInCall()) {
560                return true;
561            }
562
563            if (dialString.length() != MAX_LENGTH_SHORT_CODE ||
564                    dialString.charAt(0) != '1') {
565                return true;
566            }
567        }
568        return false;
569    }
570
571    /**
572     * @return true if the Service Code is PIN/PIN2/PUK/PUK2-related
573     */
574    boolean isPinPukCommand() {
575        return mSc != null && (mSc.equals(SC_PIN) || mSc.equals(SC_PIN2)
576                              || mSc.equals(SC_PUK) || mSc.equals(SC_PUK2));
577     }
578
579    /**
580     * See TS 22.030 Annex B.
581     * In temporary mode, to suppress CLIR for a single call, enter:
582     *      " * 31 # [called number] SEND "
583     *  In temporary mode, to invoke CLIR for a single call enter:
584     *       " # 31 # [called number] SEND "
585     */
586    boolean
587    isTemporaryModeCLIR() {
588        return mSc != null && mSc.equals(SC_CLIR) && mDialingNumber != null
589                && (isActivate() || isDeactivate());
590    }
591
592    /**
593     * returns CommandsInterface.CLIR_*
594     * See also isTemporaryModeCLIR()
595     */
596    int
597    getCLIRMode() {
598        if (mSc != null && mSc.equals(SC_CLIR)) {
599            if (isActivate()) {
600                return CommandsInterface.CLIR_SUPPRESSION;
601            } else if (isDeactivate()) {
602                return CommandsInterface.CLIR_INVOCATION;
603            }
604        }
605
606        return CommandsInterface.CLIR_DEFAULT;
607    }
608
609    boolean isActivate() {
610        return mAction != null && mAction.equals(ACTION_ACTIVATE);
611    }
612
613    boolean isDeactivate() {
614        return mAction != null && mAction.equals(ACTION_DEACTIVATE);
615    }
616
617    boolean isInterrogate() {
618        return mAction != null && mAction.equals(ACTION_INTERROGATE);
619    }
620
621    boolean isRegister() {
622        return mAction != null && mAction.equals(ACTION_REGISTER);
623    }
624
625    boolean isErasure() {
626        return mAction != null && mAction.equals(ACTION_ERASURE);
627    }
628
629    /**
630     * Returns true if this is a USSD code that's been submitted to the
631     * network...eg, after processCode() is called
632     */
633    public boolean isPendingUSSD() {
634        return mIsPendingUSSD;
635    }
636
637    @Override
638    public boolean isUssdRequest() {
639        return mIsUssdRequest;
640    }
641
642    /** Process a MMI code or short code...anything that isn't a dialing number */
643    void
644    processCode () {
645        try {
646            if (isShortCode()) {
647                Rlog.d(LOG_TAG, "isShortCode");
648                // These just get treated as USSD.
649                sendUssd(mDialingNumber);
650            } else if (mDialingNumber != null) {
651                // We should have no dialing numbers here
652                throw new RuntimeException ("Invalid or Unsupported MMI Code");
653            } else if (mSc != null && mSc.equals(SC_CLIP)) {
654                Rlog.d(LOG_TAG, "is CLIP");
655                if (isInterrogate()) {
656                    mPhone.mCi.queryCLIP(
657                            obtainMessage(EVENT_QUERY_COMPLETE, this));
658                } else {
659                    throw new RuntimeException ("Invalid or Unsupported MMI Code");
660                }
661            } else if (mSc != null && mSc.equals(SC_CLIR)) {
662                Rlog.d(LOG_TAG, "is CLIR");
663                if (isActivate()) {
664                    mPhone.mCi.setCLIR(CommandsInterface.CLIR_INVOCATION,
665                        obtainMessage(EVENT_SET_COMPLETE, this));
666                } else if (isDeactivate()) {
667                    mPhone.mCi.setCLIR(CommandsInterface.CLIR_SUPPRESSION,
668                        obtainMessage(EVENT_SET_COMPLETE, this));
669                } else if (isInterrogate()) {
670                    mPhone.mCi.getCLIR(
671                        obtainMessage(EVENT_GET_CLIR_COMPLETE, this));
672                } else {
673                    throw new RuntimeException ("Invalid or Unsupported MMI Code");
674                }
675            } else if (isServiceCodeCallForwarding(mSc)) {
676                Rlog.d(LOG_TAG, "is CF");
677
678                String dialingNumber = mSia;
679                int serviceClass = siToServiceClass(mSib);
680                int reason = scToCallForwardReason(mSc);
681                int time = siToTime(mSic);
682
683                if (isInterrogate()) {
684                    mPhone.mCi.queryCallForwardStatus(
685                            reason, serviceClass,  dialingNumber,
686                                obtainMessage(EVENT_QUERY_CF_COMPLETE, this));
687                } else {
688                    int cfAction;
689
690                    if (isActivate()) {
691                        // 3GPP TS 22.030 6.5.2
692                        // a call forwarding request with a single * would be
693                        // interpreted as registration if containing a forwarded-to
694                        // number, or an activation if not
695                        if (isEmptyOrNull(dialingNumber)) {
696                            cfAction = CommandsInterface.CF_ACTION_ENABLE;
697                            mIsCallFwdReg = false;
698                        } else {
699                            cfAction = CommandsInterface.CF_ACTION_REGISTRATION;
700                            mIsCallFwdReg = true;
701                        }
702                    } else if (isDeactivate()) {
703                        cfAction = CommandsInterface.CF_ACTION_DISABLE;
704                    } else if (isRegister()) {
705                        cfAction = CommandsInterface.CF_ACTION_REGISTRATION;
706                    } else if (isErasure()) {
707                        cfAction = CommandsInterface.CF_ACTION_ERASURE;
708                    } else {
709                        throw new RuntimeException ("invalid action");
710                    }
711
712                    int isSettingUnconditionalVoice =
713                        (((reason == CommandsInterface.CF_REASON_UNCONDITIONAL) ||
714                                (reason == CommandsInterface.CF_REASON_ALL)) &&
715                                (((serviceClass & CommandsInterface.SERVICE_CLASS_VOICE) != 0) ||
716                                 (serviceClass == CommandsInterface.SERVICE_CLASS_NONE))) ? 1 : 0;
717
718                    int isEnableDesired =
719                        ((cfAction == CommandsInterface.CF_ACTION_ENABLE) ||
720                                (cfAction == CommandsInterface.CF_ACTION_REGISTRATION)) ? 1 : 0;
721
722                    Rlog.d(LOG_TAG, "is CF setCallForward");
723                    mPhone.mCi.setCallForward(cfAction, reason, serviceClass,
724                            dialingNumber, time, obtainMessage(
725                                    EVENT_SET_CFF_COMPLETE,
726                                    isSettingUnconditionalVoice,
727                                    isEnableDesired, this));
728                }
729            } else if (isServiceCodeCallBarring(mSc)) {
730                // sia = password
731                // sib = basic service group
732
733                String password = mSia;
734                int serviceClass = siToServiceClass(mSib);
735                String facility = scToBarringFacility(mSc);
736
737                if (isInterrogate()) {
738                    mPhone.mCi.queryFacilityLock(facility, password,
739                            serviceClass, obtainMessage(EVENT_QUERY_COMPLETE, this));
740                } else if (isActivate() || isDeactivate()) {
741                    mPhone.mCi.setFacilityLock(facility, isActivate(), password,
742                            serviceClass, obtainMessage(EVENT_SET_COMPLETE, this));
743                } else {
744                    throw new RuntimeException ("Invalid or Unsupported MMI Code");
745                }
746
747            } else if (mSc != null && mSc.equals(SC_PWD)) {
748                // sia = fac
749                // sib = old pwd
750                // sic = new pwd
751                // pwd = new pwd
752                String facility;
753                String oldPwd = mSib;
754                String newPwd = mSic;
755                if (isActivate() || isRegister()) {
756                    // Even though ACTIVATE is acceptable, this is really termed a REGISTER
757                    mAction = ACTION_REGISTER;
758
759                    if (mSia == null) {
760                        // If sc was not specified, treat it as BA_ALL.
761                        facility = CommandsInterface.CB_FACILITY_BA_ALL;
762                    } else {
763                        facility = scToBarringFacility(mSia);
764                    }
765                    if (newPwd.equals(mPwd)) {
766                        mPhone.mCi.changeBarringPassword(facility, oldPwd,
767                                newPwd, obtainMessage(EVENT_SET_COMPLETE, this));
768                    } else {
769                        // password mismatch; return error
770                        handlePasswordError(com.android.internal.R.string.passwordIncorrect);
771                    }
772                } else {
773                    throw new RuntimeException ("Invalid or Unsupported MMI Code");
774                }
775
776            } else if (mSc != null && mSc.equals(SC_WAIT)) {
777                // sia = basic service group
778                int serviceClass = siToServiceClass(mSia);
779
780                if (isActivate() || isDeactivate()) {
781                    mPhone.mCi.setCallWaiting(isActivate(), serviceClass,
782                            obtainMessage(EVENT_SET_COMPLETE, this));
783                } else if (isInterrogate()) {
784                    mPhone.mCi.queryCallWaiting(serviceClass,
785                            obtainMessage(EVENT_QUERY_COMPLETE, this));
786                } else {
787                    throw new RuntimeException ("Invalid or Unsupported MMI Code");
788                }
789            } else if (isPinPukCommand()) {
790                // TODO: This is the same as the code in CmdaMmiCode.java,
791                // MmiCode should be an abstract or base class and this and
792                // other common variables and code should be promoted.
793
794                // sia = old PIN or PUK
795                // sib = new PIN
796                // sic = new PIN
797                String oldPinOrPuk = mSia;
798                String newPinOrPuk = mSib;
799                int pinLen = newPinOrPuk.length();
800                if (isRegister()) {
801                    if (!newPinOrPuk.equals(mSic)) {
802                        // password mismatch; return error
803                        handlePasswordError(com.android.internal.R.string.mismatchPin);
804                    } else if (pinLen < 4 || pinLen > 8 ) {
805                        // invalid length
806                        handlePasswordError(com.android.internal.R.string.invalidPin);
807                    } else if (mSc.equals(SC_PIN)
808                            && mUiccApplication != null
809                            && mUiccApplication.getState() == AppState.APPSTATE_PUK) {
810                        // Sim is puk-locked
811                        handlePasswordError(com.android.internal.R.string.needPuk);
812                    } else if (mUiccApplication != null) {
813                        Rlog.d(LOG_TAG, "process mmi service code using UiccApp sc=" + mSc);
814
815                        // We have an app and the pre-checks are OK
816                        if (mSc.equals(SC_PIN)) {
817                            mUiccApplication.changeIccLockPassword(oldPinOrPuk, newPinOrPuk,
818                                    obtainMessage(EVENT_SET_COMPLETE, this));
819                        } else if (mSc.equals(SC_PIN2)) {
820                            mUiccApplication.changeIccFdnPassword(oldPinOrPuk, newPinOrPuk,
821                                    obtainMessage(EVENT_SET_COMPLETE, this));
822                        } else if (mSc.equals(SC_PUK)) {
823                            mUiccApplication.supplyPuk(oldPinOrPuk, newPinOrPuk,
824                                    obtainMessage(EVENT_SET_COMPLETE, this));
825                        } else if (mSc.equals(SC_PUK2)) {
826                            mUiccApplication.supplyPuk2(oldPinOrPuk, newPinOrPuk,
827                                    obtainMessage(EVENT_SET_COMPLETE, this));
828                        } else {
829                            throw new RuntimeException("uicc unsupported service code=" + mSc);
830                        }
831                    } else {
832                        throw new RuntimeException("No application mUiccApplicaiton is null");
833                    }
834                } else {
835                    throw new RuntimeException ("Ivalid register/action=" + mAction);
836                }
837            } else if (mPoundString != null) {
838                sendUssd(mPoundString);
839            } else {
840                throw new RuntimeException ("Invalid or Unsupported MMI Code");
841            }
842        } catch (RuntimeException exc) {
843            mState = State.FAILED;
844            mMessage = mContext.getText(com.android.internal.R.string.mmiError);
845            mPhone.onMMIDone(this);
846        }
847    }
848
849    private void handlePasswordError(int res) {
850        mState = State.FAILED;
851        StringBuilder sb = new StringBuilder(getScString());
852        sb.append("\n");
853        sb.append(mContext.getText(res));
854        mMessage = sb;
855        mPhone.onMMIDone(this);
856    }
857
858    /**
859     * Called from GSMPhone
860     *
861     * An unsolicited USSD NOTIFY or REQUEST has come in matching
862     * up with this pending USSD request
863     *
864     * Note: If REQUEST, this exchange is complete, but the session remains
865     *       active (ie, the network expects user input).
866     */
867    void
868    onUssdFinished(String ussdMessage, boolean isUssdRequest) {
869        if (mState == State.PENDING) {
870            if (ussdMessage == null) {
871                mMessage = mContext.getText(com.android.internal.R.string.mmiComplete);
872            } else {
873                mMessage = ussdMessage;
874            }
875            mIsUssdRequest = isUssdRequest;
876            // If it's a request, leave it PENDING so that it's cancelable.
877            if (!isUssdRequest) {
878                mState = State.COMPLETE;
879            }
880
881            mPhone.onMMIDone(this);
882        }
883    }
884
885    /**
886     * Called from GSMPhone
887     *
888     * The radio has reset, and this is still pending
889     */
890
891    void
892    onUssdFinishedError() {
893        if (mState == State.PENDING) {
894            mState = State.FAILED;
895            mMessage = mContext.getText(com.android.internal.R.string.mmiError);
896
897            mPhone.onMMIDone(this);
898        }
899    }
900
901    void sendUssd(String ussdMessage) {
902        // Treat this as a USSD string
903        mIsPendingUSSD = true;
904
905        // Note that unlike most everything else, the USSD complete
906        // response does not complete this MMI code...we wait for
907        // an unsolicited USSD "Notify" or "Request".
908        // The matching up of this is done in GSMPhone.
909
910        mPhone.mCi.sendUSSD(ussdMessage,
911            obtainMessage(EVENT_USSD_COMPLETE, this));
912    }
913
914    /** Called from GSMPhone.handleMessage; not a Handler subclass */
915    @Override
916    public void
917    handleMessage (Message msg) {
918        AsyncResult ar;
919
920        switch (msg.what) {
921            case EVENT_SET_COMPLETE:
922                ar = (AsyncResult) (msg.obj);
923
924                onSetComplete(msg, ar);
925                break;
926
927            case EVENT_SET_CFF_COMPLETE:
928                ar = (AsyncResult) (msg.obj);
929
930                /*
931                * msg.arg1 = 1 means to set unconditional voice call forwarding
932                * msg.arg2 = 1 means to enable voice call forwarding
933                */
934                if ((ar.exception == null) && (msg.arg1 == 1)) {
935                    boolean cffEnabled = (msg.arg2 == 1);
936                    if (mIccRecords != null) {
937                        mIccRecords.setVoiceCallForwardingFlag(1, cffEnabled, mDialingNumber);
938                    }
939                }
940
941                onSetComplete(msg, ar);
942                break;
943
944            case EVENT_GET_CLIR_COMPLETE:
945                ar = (AsyncResult) (msg.obj);
946                onGetClirComplete(ar);
947            break;
948
949            case EVENT_QUERY_CF_COMPLETE:
950                ar = (AsyncResult) (msg.obj);
951                onQueryCfComplete(ar);
952            break;
953
954            case EVENT_QUERY_COMPLETE:
955                ar = (AsyncResult) (msg.obj);
956                onQueryComplete(ar);
957            break;
958
959            case EVENT_USSD_COMPLETE:
960                ar = (AsyncResult) (msg.obj);
961
962                if (ar.exception != null) {
963                    mState = State.FAILED;
964                    mMessage = getErrorMessage(ar);
965
966                    mPhone.onMMIDone(this);
967                }
968
969                // Note that unlike most everything else, the USSD complete
970                // response does not complete this MMI code...we wait for
971                // an unsolicited USSD "Notify" or "Request".
972                // The matching up of this is done in GSMPhone.
973
974            break;
975
976            case EVENT_USSD_CANCEL_COMPLETE:
977                mPhone.onMMIDone(this);
978            break;
979        }
980    }
981    //***** Private instance methods
982
983    private CharSequence getErrorMessage(AsyncResult ar) {
984
985        if (ar.exception instanceof CommandException) {
986            CommandException.Error err = ((CommandException)(ar.exception)).getCommandError();
987            if (err == CommandException.Error.FDN_CHECK_FAILURE) {
988                Rlog.i(LOG_TAG, "FDN_CHECK_FAILURE");
989                return mContext.getText(com.android.internal.R.string.mmiFdnError);
990            }
991        }
992
993        return mContext.getText(com.android.internal.R.string.mmiError);
994    }
995
996    private CharSequence getScString() {
997        if (mSc != null) {
998            if (isServiceCodeCallBarring(mSc)) {
999                return mContext.getText(com.android.internal.R.string.BaMmi);
1000            } else if (isServiceCodeCallForwarding(mSc)) {
1001                return mContext.getText(com.android.internal.R.string.CfMmi);
1002            } else if (mSc.equals(SC_CLIP)) {
1003                return mContext.getText(com.android.internal.R.string.ClipMmi);
1004            } else if (mSc.equals(SC_CLIR)) {
1005                return mContext.getText(com.android.internal.R.string.ClirMmi);
1006            } else if (mSc.equals(SC_PWD)) {
1007                return mContext.getText(com.android.internal.R.string.PwdMmi);
1008            } else if (mSc.equals(SC_WAIT)) {
1009                return mContext.getText(com.android.internal.R.string.CwMmi);
1010            } else if (isPinPukCommand()) {
1011                return mContext.getText(com.android.internal.R.string.PinMmi);
1012            }
1013        }
1014
1015        return "";
1016    }
1017
1018    private void
1019    onSetComplete(Message msg, AsyncResult ar){
1020        StringBuilder sb = new StringBuilder(getScString());
1021        sb.append("\n");
1022
1023        if (ar.exception != null) {
1024            mState = State.FAILED;
1025            if (ar.exception instanceof CommandException) {
1026                CommandException.Error err = ((CommandException)(ar.exception)).getCommandError();
1027                if (err == CommandException.Error.PASSWORD_INCORRECT) {
1028                    if (isPinPukCommand()) {
1029                        // look specifically for the PUK commands and adjust
1030                        // the message accordingly.
1031                        if (mSc.equals(SC_PUK) || mSc.equals(SC_PUK2)) {
1032                            sb.append(mContext.getText(
1033                                    com.android.internal.R.string.badPuk));
1034                        } else {
1035                            sb.append(mContext.getText(
1036                                    com.android.internal.R.string.badPin));
1037                        }
1038                        // Get the No. of retries remaining to unlock PUK/PUK2
1039                        int attemptsRemaining = msg.arg1;
1040                        if (attemptsRemaining <= 0) {
1041                            Rlog.d(LOG_TAG, "onSetComplete: PUK locked,"
1042                                    + " cancel as lock screen will handle this");
1043                            mState = State.CANCELLED;
1044                        } else if (attemptsRemaining > 0) {
1045                            Rlog.d(LOG_TAG, "onSetComplete: attemptsRemaining="+attemptsRemaining);
1046                            sb.append(mContext.getResources().getQuantityString(
1047                                    com.android.internal.R.plurals.pinpuk_attempts,
1048                                    attemptsRemaining, attemptsRemaining));
1049                        }
1050                    } else {
1051                        sb.append(mContext.getText(
1052                                com.android.internal.R.string.passwordIncorrect));
1053                    }
1054                } else if (err == CommandException.Error.SIM_PUK2) {
1055                    sb.append(mContext.getText(
1056                            com.android.internal.R.string.badPin));
1057                    sb.append("\n");
1058                    sb.append(mContext.getText(
1059                            com.android.internal.R.string.needPuk2));
1060                } else if (err == CommandException.Error.REQUEST_NOT_SUPPORTED) {
1061                    if (mSc.equals(SC_PIN)) {
1062                        sb.append(mContext.getText(com.android.internal.R.string.enablePin));
1063                    }
1064                } else if (err == CommandException.Error.FDN_CHECK_FAILURE) {
1065                    Rlog.i(LOG_TAG, "FDN_CHECK_FAILURE");
1066                    sb.append(mContext.getText(com.android.internal.R.string.mmiFdnError));
1067                } else {
1068                    sb.append(mContext.getText(
1069                            com.android.internal.R.string.mmiError));
1070                }
1071            } else {
1072                sb.append(mContext.getText(
1073                        com.android.internal.R.string.mmiError));
1074            }
1075        } else if (isActivate()) {
1076            mState = State.COMPLETE;
1077            if (mIsCallFwdReg) {
1078                sb.append(mContext.getText(
1079                        com.android.internal.R.string.serviceRegistered));
1080            } else {
1081                sb.append(mContext.getText(
1082                        com.android.internal.R.string.serviceEnabled));
1083            }
1084            // Record CLIR setting
1085            if (mSc.equals(SC_CLIR)) {
1086                mPhone.saveClirSetting(CommandsInterface.CLIR_INVOCATION);
1087            }
1088        } else if (isDeactivate()) {
1089            mState = State.COMPLETE;
1090            sb.append(mContext.getText(
1091                    com.android.internal.R.string.serviceDisabled));
1092            // Record CLIR setting
1093            if (mSc.equals(SC_CLIR)) {
1094                mPhone.saveClirSetting(CommandsInterface.CLIR_SUPPRESSION);
1095            }
1096        } else if (isRegister()) {
1097            mState = State.COMPLETE;
1098            sb.append(mContext.getText(
1099                    com.android.internal.R.string.serviceRegistered));
1100        } else if (isErasure()) {
1101            mState = State.COMPLETE;
1102            sb.append(mContext.getText(
1103                    com.android.internal.R.string.serviceErased));
1104        } else {
1105            mState = State.FAILED;
1106            sb.append(mContext.getText(
1107                    com.android.internal.R.string.mmiError));
1108        }
1109
1110        mMessage = sb;
1111        mPhone.onMMIDone(this);
1112    }
1113
1114    private void
1115    onGetClirComplete(AsyncResult ar) {
1116        StringBuilder sb = new StringBuilder(getScString());
1117        sb.append("\n");
1118
1119        if (ar.exception != null) {
1120            mState = State.FAILED;
1121            sb.append(getErrorMessage(ar));
1122        } else {
1123            int clirArgs[];
1124
1125            clirArgs = (int[])ar.result;
1126
1127            // the 'm' parameter from TS 27.007 7.7
1128            switch (clirArgs[1]) {
1129                case 0: // CLIR not provisioned
1130                    sb.append(mContext.getText(
1131                                com.android.internal.R.string.serviceNotProvisioned));
1132                    mState = State.COMPLETE;
1133                break;
1134
1135                case 1: // CLIR provisioned in permanent mode
1136                    sb.append(mContext.getText(
1137                                com.android.internal.R.string.CLIRPermanent));
1138                    mState = State.COMPLETE;
1139                break;
1140
1141                case 2: // unknown (e.g. no network, etc.)
1142                    sb.append(mContext.getText(
1143                                com.android.internal.R.string.mmiError));
1144                    mState = State.FAILED;
1145                break;
1146
1147                case 3: // CLIR temporary mode presentation restricted
1148
1149                    // the 'n' parameter from TS 27.007 7.7
1150                    switch (clirArgs[0]) {
1151                        default:
1152                        case 0: // Default
1153                            sb.append(mContext.getText(
1154                                    com.android.internal.R.string.CLIRDefaultOnNextCallOn));
1155                        break;
1156                        case 1: // CLIR invocation
1157                            sb.append(mContext.getText(
1158                                    com.android.internal.R.string.CLIRDefaultOnNextCallOn));
1159                        break;
1160                        case 2: // CLIR suppression
1161                            sb.append(mContext.getText(
1162                                    com.android.internal.R.string.CLIRDefaultOnNextCallOff));
1163                        break;
1164                    }
1165                    mState = State.COMPLETE;
1166                break;
1167
1168                case 4: // CLIR temporary mode presentation allowed
1169                    // the 'n' parameter from TS 27.007 7.7
1170                    switch (clirArgs[0]) {
1171                        default:
1172                        case 0: // Default
1173                            sb.append(mContext.getText(
1174                                    com.android.internal.R.string.CLIRDefaultOffNextCallOff));
1175                        break;
1176                        case 1: // CLIR invocation
1177                            sb.append(mContext.getText(
1178                                    com.android.internal.R.string.CLIRDefaultOffNextCallOn));
1179                        break;
1180                        case 2: // CLIR suppression
1181                            sb.append(mContext.getText(
1182                                    com.android.internal.R.string.CLIRDefaultOffNextCallOff));
1183                        break;
1184                    }
1185
1186                    mState = State.COMPLETE;
1187                break;
1188            }
1189        }
1190
1191        mMessage = sb;
1192        mPhone.onMMIDone(this);
1193    }
1194
1195    /**
1196     * @param serviceClass 1 bit of the service class bit vectory
1197     * @return String to be used for call forward query MMI response text.
1198     *        Returns null if unrecognized
1199     */
1200
1201    private CharSequence
1202    serviceClassToCFString (int serviceClass) {
1203        switch (serviceClass) {
1204            case SERVICE_CLASS_VOICE:
1205                return mContext.getText(com.android.internal.R.string.serviceClassVoice);
1206            case SERVICE_CLASS_DATA:
1207                return mContext.getText(com.android.internal.R.string.serviceClassData);
1208            case SERVICE_CLASS_FAX:
1209                return mContext.getText(com.android.internal.R.string.serviceClassFAX);
1210            case SERVICE_CLASS_SMS:
1211                return mContext.getText(com.android.internal.R.string.serviceClassSMS);
1212            case SERVICE_CLASS_DATA_SYNC:
1213                return mContext.getText(com.android.internal.R.string.serviceClassDataSync);
1214            case SERVICE_CLASS_DATA_ASYNC:
1215                return mContext.getText(com.android.internal.R.string.serviceClassDataAsync);
1216            case SERVICE_CLASS_PACKET:
1217                return mContext.getText(com.android.internal.R.string.serviceClassPacket);
1218            case SERVICE_CLASS_PAD:
1219                return mContext.getText(com.android.internal.R.string.serviceClassPAD);
1220            default:
1221                return null;
1222        }
1223    }
1224
1225
1226    /** one CallForwardInfo + serviceClassMask -> one line of text */
1227    private CharSequence
1228    makeCFQueryResultMessage(CallForwardInfo info, int serviceClassMask) {
1229        CharSequence template;
1230        String sources[] = {"{0}", "{1}", "{2}"};
1231        CharSequence destinations[] = new CharSequence[3];
1232        boolean needTimeTemplate;
1233
1234        // CF_REASON_NO_REPLY also has a time value associated with
1235        // it. All others don't.
1236
1237        needTimeTemplate =
1238            (info.reason == CommandsInterface.CF_REASON_NO_REPLY);
1239
1240        if (info.status == 1) {
1241            if (needTimeTemplate) {
1242                template = mContext.getText(
1243                        com.android.internal.R.string.cfTemplateForwardedTime);
1244            } else {
1245                template = mContext.getText(
1246                        com.android.internal.R.string.cfTemplateForwarded);
1247            }
1248        } else if (info.status == 0 && isEmptyOrNull(info.number)) {
1249            template = mContext.getText(
1250                        com.android.internal.R.string.cfTemplateNotForwarded);
1251        } else { /* (info.status == 0) && !isEmptyOrNull(info.number) */
1252            // A call forward record that is not active but contains
1253            // a phone number is considered "registered"
1254
1255            if (needTimeTemplate) {
1256                template = mContext.getText(
1257                        com.android.internal.R.string.cfTemplateRegisteredTime);
1258            } else {
1259                template = mContext.getText(
1260                        com.android.internal.R.string.cfTemplateRegistered);
1261            }
1262        }
1263
1264        // In the template (from strings.xmls)
1265        //         {0} is one of "bearerServiceCode*"
1266        //        {1} is dialing number
1267        //      {2} is time in seconds
1268
1269        destinations[0] = serviceClassToCFString(info.serviceClass & serviceClassMask);
1270        destinations[1] = formatLtr(
1271                PhoneNumberUtils.stringFromStringAndTOA(info.number, info.toa));
1272        destinations[2] = Integer.toString(info.timeSeconds);
1273
1274        if (info.reason == CommandsInterface.CF_REASON_UNCONDITIONAL &&
1275                (info.serviceClass & serviceClassMask)
1276                        == CommandsInterface.SERVICE_CLASS_VOICE) {
1277            boolean cffEnabled = (info.status == 1);
1278            if (mIccRecords != null) {
1279                mIccRecords.setVoiceCallForwardingFlag(1, cffEnabled, info.number);
1280            }
1281        }
1282
1283        return TextUtils.replace(template, sources, destinations);
1284    }
1285
1286    /**
1287     * Used to format a string that should be displayed as LTR even in RTL locales
1288     */
1289    private String formatLtr(String str) {
1290        BidiFormatter fmt = BidiFormatter.getInstance();
1291        return str == null ? str : fmt.unicodeWrap(str, TextDirectionHeuristics.LTR, true);
1292    }
1293
1294    private void
1295    onQueryCfComplete(AsyncResult ar) {
1296        StringBuilder sb = new StringBuilder(getScString());
1297        sb.append("\n");
1298
1299        if (ar.exception != null) {
1300            mState = State.FAILED;
1301            sb.append(getErrorMessage(ar));
1302        } else {
1303            CallForwardInfo infos[];
1304
1305            infos = (CallForwardInfo[]) ar.result;
1306
1307            if (infos.length == 0) {
1308                // Assume the default is not active
1309                sb.append(mContext.getText(com.android.internal.R.string.serviceDisabled));
1310
1311                // Set unconditional CFF in SIM to false
1312                if (mIccRecords != null) {
1313                    mIccRecords.setVoiceCallForwardingFlag(1, false, null);
1314                }
1315            } else {
1316
1317                SpannableStringBuilder tb = new SpannableStringBuilder();
1318
1319                // Each bit in the service class gets its own result line
1320                // The service classes may be split up over multiple
1321                // CallForwardInfos. So, for each service class, find out
1322                // which CallForwardInfo represents it and then build
1323                // the response text based on that
1324
1325                for (int serviceClassMask = 1
1326                            ; serviceClassMask <= SERVICE_CLASS_MAX
1327                            ; serviceClassMask <<= 1
1328                ) {
1329                    for (int i = 0, s = infos.length; i < s ; i++) {
1330                        if ((serviceClassMask & infos[i].serviceClass) != 0) {
1331                            tb.append(makeCFQueryResultMessage(infos[i],
1332                                            serviceClassMask));
1333                            tb.append("\n");
1334                        }
1335                    }
1336                }
1337                sb.append(tb);
1338            }
1339
1340            mState = State.COMPLETE;
1341        }
1342
1343        mMessage = sb;
1344        mPhone.onMMIDone(this);
1345
1346    }
1347
1348    private void
1349    onQueryComplete(AsyncResult ar) {
1350        StringBuilder sb = new StringBuilder(getScString());
1351        sb.append("\n");
1352
1353        if (ar.exception != null) {
1354            mState = State.FAILED;
1355            sb.append(getErrorMessage(ar));
1356        } else {
1357            int[] ints = (int[])ar.result;
1358
1359            if (ints.length != 0) {
1360                if (ints[0] == 0) {
1361                    sb.append(mContext.getText(com.android.internal.R.string.serviceDisabled));
1362                } else if (mSc.equals(SC_WAIT)) {
1363                    // Call Waiting includes additional data in the response.
1364                    sb.append(createQueryCallWaitingResultMessage(ints[1]));
1365                } else if (isServiceCodeCallBarring(mSc)) {
1366                    // ints[0] for Call Barring is a bit vector of services
1367                    sb.append(createQueryCallBarringResultMessage(ints[0]));
1368                } else if (ints[0] == 1) {
1369                    // for all other services, treat it as a boolean
1370                    sb.append(mContext.getText(com.android.internal.R.string.serviceEnabled));
1371                } else {
1372                    sb.append(mContext.getText(com.android.internal.R.string.mmiError));
1373                }
1374            } else {
1375                sb.append(mContext.getText(com.android.internal.R.string.mmiError));
1376            }
1377            mState = State.COMPLETE;
1378        }
1379
1380        mMessage = sb;
1381        mPhone.onMMIDone(this);
1382    }
1383
1384    private CharSequence
1385    createQueryCallWaitingResultMessage(int serviceClass) {
1386        StringBuilder sb =
1387                new StringBuilder(mContext.getText(com.android.internal.R.string.serviceEnabledFor));
1388
1389        for (int classMask = 1
1390                    ; classMask <= SERVICE_CLASS_MAX
1391                    ; classMask <<= 1
1392        ) {
1393            if ((classMask & serviceClass) != 0) {
1394                sb.append("\n");
1395                sb.append(serviceClassToCFString(classMask & serviceClass));
1396            }
1397        }
1398        return sb;
1399    }
1400    private CharSequence
1401    createQueryCallBarringResultMessage(int serviceClass)
1402    {
1403        StringBuilder sb = new StringBuilder(mContext.getText(com.android.internal.R.string.serviceEnabledFor));
1404
1405        for (int classMask = 1
1406                    ; classMask <= SERVICE_CLASS_MAX
1407                    ; classMask <<= 1
1408        ) {
1409            if ((classMask & serviceClass) != 0) {
1410                sb.append("\n");
1411                sb.append(serviceClassToCFString(classMask & serviceClass));
1412            }
1413        }
1414        return sb;
1415    }
1416
1417    /***
1418     * TODO: It would be nice to have a method here that can take in a dialstring and
1419     * figure out if there is an MMI code embedded within it.  This code would replace
1420     * some of the string parsing functionality in the Phone App's
1421     * SpecialCharSequenceMgr class.
1422     */
1423
1424    @Override
1425    public String toString() {
1426        StringBuilder sb = new StringBuilder("GsmMmiCode {");
1427
1428        sb.append("State=" + getState());
1429        if (mAction != null) sb.append(" action=" + mAction);
1430        if (mSc != null) sb.append(" sc=" + mSc);
1431        if (mSia != null) sb.append(" sia=" + mSia);
1432        if (mSib != null) sb.append(" sib=" + mSib);
1433        if (mSic != null) sb.append(" sic=" + mSic);
1434        if (mPoundString != null) sb.append(" poundString=" + mPoundString);
1435        if (mDialingNumber != null) sb.append(" dialingNumber=" + mDialingNumber);
1436        if (mPwd != null) sb.append(" pwd=" + mPwd);
1437        sb.append("}");
1438        return sb.toString();
1439    }
1440}
1441