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     GsmCdmaPhone 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     public 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     public static GsmMmiCode
newFromDialString(String dialString, GsmCdmaPhone phone, UiccCardApplication app)188     newFromDialString(String dialString, GsmCdmaPhone 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     public static GsmMmiCode
newNetworkInitiatedUssd(String ussdMessage, boolean isUssdRequest, GsmCdmaPhone phone, UiccCardApplication app)236     newNetworkInitiatedUssd(String ussdMessage,
237                             boolean isUssdRequest, GsmCdmaPhone 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 
newFromUssdUserInput(String ussdMessge, GsmCdmaPhone phone, UiccCardApplication app)256     public static GsmMmiCode newFromUssdUserInput(String ussdMessge,
257                                                   GsmCdmaPhone 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     public void
processSsData(AsyncResult data)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 
parseSsData(SsData ssData)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                         mPhone.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 
getScStringFromScType(SsData.ServiceType sType)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 
getActionStringFromReqType(SsData.RequestType rType)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 
isServiceClassVoiceorNone(int serviceClass)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
makeEmptyNull(String s)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
isEmptyOrNull(CharSequence s)412     isEmptyOrNull(CharSequence s) {
413         return s == null || (s.length() == 0);
414     }
415 
416 
417     private static int
scToCallForwardReason(String sc)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
siToServiceClass(String si)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
siToTime(String si)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
isServiceCodeCallForwarding(String sc)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
isServiceCodeCallBarring(String sc)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
scToBarringFacility(String sc)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 
GsmMmiCode(GsmCdmaPhone phone, UiccCardApplication app)542     public GsmMmiCode(GsmCdmaPhone 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
getState()558     getState() {
559         return mState;
560     }
561 
562     @Override
563     public CharSequence
getMessage()564     getMessage() {
565         return mMessage;
566     }
567 
568     public Phone
getPhone()569     getPhone() {
570         return ((Phone) mPhone);
571     }
572 
573     // inherited javadoc suffices
574     @Override
575     public void
cancel()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
isCancelable()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
isMMI()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
isShortCode()621     isShortCode() {
622         return mPoundString == null
623                     && mDialingNumber != null && mDialingNumber.length() <= 2;
624 
625     }
626 
627     static private boolean
isTwoDigitShortCode(Context context, String dialString)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      */
isShortCode(String dialString, GsmCdmaPhone phone)654     static private boolean isShortCode(String dialString, GsmCdmaPhone 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      */
isShortCodeUSSD(String dialString, GsmCdmaPhone phone)690     static private boolean isShortCodeUSSD(String dialString, GsmCdmaPhone 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      */
isPinPukCommand()707     public 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     public boolean
isTemporaryModeCLIR()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     public int
getCLIRMode()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 
isActivate()742     boolean isActivate() {
743         return mAction != null && mAction.equals(ACTION_ACTIVATE);
744     }
745 
isDeactivate()746     boolean isDeactivate() {
747         return mAction != null && mAction.equals(ACTION_DEACTIVATE);
748     }
749 
isInterrogate()750     boolean isInterrogate() {
751         return mAction != null && mAction.equals(ACTION_INTERROGATE);
752     }
753 
isRegister()754     boolean isRegister() {
755         return mAction != null && mAction.equals(ACTION_REGISTER);
756     }
757 
isErasure()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      */
isPendingUSSD()766     public boolean isPendingUSSD() {
767         return mIsPendingUSSD;
768     }
769 
770     @Override
isUssdRequest()771     public boolean isUssdRequest() {
772         return mIsUssdRequest;
773     }
774 
isSsInfo()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     public void
processCode()781     processCode() throws CallStateException {
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 
handlePasswordError(int res)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 GsmCdmaPhone
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     public void
onUssdFinished(String ussdMessage, boolean isUssdRequest)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 GsmCdmaPhone
1024      *
1025      * The radio has reset, and this is still pending
1026      */
1027 
1028     public void
onUssdFinishedError()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 GsmCdmaPhone
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     public void
onUssdRelease()1048     onUssdRelease() {
1049         if (mState == State.PENDING) {
1050             mState = State.COMPLETE;
1051             mMessage = null;
1052 
1053             mPhone.onMMIDone(this);
1054         }
1055     }
1056 
sendUssd(String ussdMessage)1057     public 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 GsmCdmaPhone.
1065 
1066         mPhone.mCi.sendUSSD(ussdMessage,
1067             obtainMessage(EVENT_USSD_COMPLETE, this));
1068     }
1069 
1070     /** Called from GsmCdmaPhone.handleMessage; not a Handler subclass */
1071     @Override
1072     public void
handleMessage(Message msg)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                         mPhone.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 GsmCdmaPhone.
1129 
1130             break;
1131 
1132             case EVENT_USSD_CANCEL_COMPLETE:
1133                 mPhone.onMMIDone(this);
1134             break;
1135         }
1136     }
1137     //***** Private instance methods
1138 
getErrorMessage(AsyncResult ar)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 
getScString()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
onSetComplete(Message msg, AsyncResult ar)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
onGetClirComplete(AsyncResult ar)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
serviceClassToCFString(int serviceClass)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
makeCFQueryResultMessage(CallForwardInfo info, int serviceClassMask)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                 mPhone.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      */
formatLtr(String str)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
onQueryCfComplete(AsyncResult ar)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                     mPhone.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
onQueryComplete(AsyncResult ar)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
createQueryCallWaitingResultMessage(int serviceClass)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
createQueryCallBarringResultMessage(int serviceClass)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
toString()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