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