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