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