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