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