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