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