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