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