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