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