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