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