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