OSDN Git Service

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