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     // 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 which must not include #, e.g. *SCn*SI#DN format
    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.isLocalEmergencyNumber(dialString, phone.getContext())) {
    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     @Override
   1342     public String toString() {
   1343         StringBuilder sb = new StringBuilder("GsmMmiCode {");
   1344 
   1345         sb.append("State=" + getState());
   1346         if (action != null) sb.append(" action=" + action);
   1347         if (sc != null) sb.append(" sc=" + sc);
   1348         if (sia != null) sb.append(" sia=" + sia);
   1349         if (sib != null) sb.append(" sib=" + sib);
   1350         if (sic != null) sb.append(" sic=" + sic);
   1351         if (poundString != null) sb.append(" poundString=" + poundString);
   1352         if (dialingNumber != null) sb.append(" dialingNumber=" + dialingNumber);
   1353         if (pwd != null) sb.append(" pwd=" + pwd);
   1354         sb.append("}");
   1355         return sb.toString();
   1356     }
   1357 }
   1358