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