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