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