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