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