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