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