• 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                 Rlog.d(LOG_TAG, "processCode: Sending short code '"
895                        + mDialingNumber + "' over CS pipe.");
896                 throw new CallStateException(Phone.CS_FALLBACK);
897             } else if (isServiceCodeCallForwarding(mSc)) {
898                 Rlog.d(LOG_TAG, "processCode: is CF");
899 
900                 String dialingNumber = mSia;
901                 int reason = scToCallForwardReason(mSc);
902                 int serviceClass = siToServiceClass(mSib);
903                 int time = siToTime(mSic);
904 
905                 if (isInterrogate()) {
906                     mPhone.getCallForwardingOption(reason, serviceClass,
907                             obtainMessage(EVENT_QUERY_CF_COMPLETE, this));
908                 } else {
909                     int cfAction;
910 
911                     if (isActivate()) {
912                         // 3GPP TS 22.030 6.5.2
913                         // a call forwarding request with a single * would be
914                         // interpreted as registration if containing a forwarded-to
915                         // number, or an activation if not
916                         if (isEmptyOrNull(dialingNumber)) {
917                             cfAction = CommandsInterface.CF_ACTION_ENABLE;
918                             mIsCallFwdReg = false;
919                         } else {
920                             cfAction = CommandsInterface.CF_ACTION_REGISTRATION;
921                             mIsCallFwdReg = true;
922                         }
923                     } else if (isDeactivate()) {
924                         cfAction = CommandsInterface.CF_ACTION_DISABLE;
925                     } else if (isRegister()) {
926                         cfAction = CommandsInterface.CF_ACTION_REGISTRATION;
927                     } else if (isErasure()) {
928                         cfAction = CommandsInterface.CF_ACTION_ERASURE;
929                     } else {
930                         throw new RuntimeException ("invalid action");
931                     }
932 
933                     int isSettingUnconditional =
934                             ((reason == CommandsInterface.CF_REASON_UNCONDITIONAL) ||
935                              (reason == CommandsInterface.CF_REASON_ALL)) ? 1 : 0;
936 
937                     int isEnableDesired =
938                         ((cfAction == CommandsInterface.CF_ACTION_ENABLE) ||
939                                 (cfAction == CommandsInterface.CF_ACTION_REGISTRATION)) ? 1 : 0;
940 
941                     Rlog.d(LOG_TAG, "processCode: is CF setCallForward");
942                     mPhone.setCallForwardingOption(cfAction, reason,
943                             dialingNumber, serviceClass, time, obtainMessage(
944                                     EVENT_SET_CFF_COMPLETE,
945                                     isSettingUnconditional,
946                                     isEnableDesired, this));
947                 }
948             } else if (isServiceCodeCallBarring(mSc)) {
949                 // sia = password
950                 // sib = basic service group
951                 // service group is not supported
952 
953                 String password = mSia;
954                 String facility = scToBarringFacility(mSc);
955                 int serviceClass = siToServiceClass(mSib);
956 
957                 if (isInterrogate()) {
958                     mPhone.getCallBarring(facility,
959                             obtainMessage(EVENT_SUPP_SVC_QUERY_COMPLETE, this), serviceClass);
960                 } else if (isActivate() || isDeactivate()) {
961                     mPhone.setCallBarring(facility, isActivate(), password,
962                             obtainMessage(EVENT_SET_COMPLETE, this), serviceClass);
963                 } else {
964                     throw new RuntimeException ("Invalid or Unsupported MMI Code");
965                 }
966             } else if (mSc != null && mSc.equals(SC_CLIR)) {
967                 // NOTE: Since these supplementary services are accessed only
968                 //       via MMI codes, methods have not been added to ImsPhone.
969                 //       Only the UT interface handle is used.
970                 if (isActivate()
971                         && !mPhone.getDefaultPhone().isClirActivationAndDeactivationPrevented()) {
972                     try {
973                         mPhone.setOutgoingCallerIdDisplay(CommandsInterface.CLIR_INVOCATION,
974                             obtainMessage(EVENT_SET_COMPLETE, this));
975                     } catch (Exception e) {
976                         Rlog.d(LOG_TAG, "processCode: Could not get UT handle for updateCLIR.");
977                     }
978                 } else if (isDeactivate()
979                         && !mPhone.getDefaultPhone().isClirActivationAndDeactivationPrevented()) {
980                     try {
981                         mPhone.setOutgoingCallerIdDisplay(CommandsInterface.CLIR_SUPPRESSION,
982                             obtainMessage(EVENT_SET_COMPLETE, this));
983                     } catch (Exception e) {
984                         Rlog.d(LOG_TAG, "processCode: Could not get UT handle for updateCLIR.");
985                     }
986                 } else if (isInterrogate()) {
987                     try {
988                         mPhone.getOutgoingCallerIdDisplay(obtainMessage(EVENT_GET_CLIR_COMPLETE, this));
989                     } catch (Exception e) {
990                         Rlog.d(LOG_TAG, "processCode: Could not get UT handle for queryCLIR.");
991                     }
992                 } else {
993                     throw new RuntimeException ("Invalid or Unsupported MMI Code");
994                 }
995             } else if (mSc != null && mSc.equals(SC_CLIP)) {
996                 // NOTE: Refer to the note above.
997                 if (isInterrogate()) {
998                     try {
999                         mPhone.queryCLIP(obtainMessage(EVENT_SUPP_SVC_QUERY_COMPLETE, this));
1000                     } catch (Exception e) {
1001                         Rlog.d(LOG_TAG, "processCode: Could not get UT handle for queryCLIP.");
1002                     }
1003                 } else if (isActivate() || isDeactivate()) {
1004                     try {
1005                         mPhone.mCT.getUtInterface().updateCLIP(isActivate(),
1006                                 obtainMessage(EVENT_SET_COMPLETE, this));
1007                     } catch (ImsException e) {
1008                         Rlog.d(LOG_TAG, "processCode: Could not get UT handle for updateCLIP.");
1009                     }
1010                 } else {
1011                     throw new RuntimeException ("Invalid or Unsupported MMI Code");
1012                 }
1013             } else if (mSc != null && mSc.equals(SC_COLP)) {
1014                 // NOTE: Refer to the note above.
1015                 if (isInterrogate()) {
1016                     try {
1017                         mPhone.mCT.getUtInterface()
1018                             .queryCOLP(obtainMessage(EVENT_SUPP_SVC_QUERY_COMPLETE, this));
1019                     } catch (ImsException e) {
1020                         Rlog.d(LOG_TAG, "processCode: Could not get UT handle for queryCOLP.");
1021                     }
1022                 } else if (isActivate() || isDeactivate()) {
1023                     try {
1024                         mPhone.mCT.getUtInterface().updateCOLP(isActivate(),
1025                                  obtainMessage(EVENT_SET_COMPLETE, this));
1026                      } catch (ImsException e) {
1027                          Rlog.d(LOG_TAG, "processCode: Could not get UT handle for updateCOLP.");
1028                      }
1029                 } else {
1030                     throw new RuntimeException ("Invalid or Unsupported MMI Code");
1031                 }
1032             } else if (mSc != null && mSc.equals(SC_COLR)) {
1033                 // NOTE: Refer to the note above.
1034                 if (isActivate()) {
1035                     try {
1036                         mPhone.mCT.getUtInterface().updateCOLR(NUM_PRESENTATION_RESTRICTED,
1037                                 obtainMessage(EVENT_SET_COMPLETE, this));
1038                     } catch (ImsException e) {
1039                         Rlog.d(LOG_TAG, "processCode: Could not get UT handle for updateCOLR.");
1040                     }
1041                 } else if (isDeactivate()) {
1042                     try {
1043                         mPhone.mCT.getUtInterface().updateCOLR(NUM_PRESENTATION_ALLOWED,
1044                                 obtainMessage(EVENT_SET_COMPLETE, this));
1045                     } catch (ImsException e) {
1046                         Rlog.d(LOG_TAG, "processCode: Could not get UT handle for updateCOLR.");
1047                     }
1048                 } else if (isInterrogate()) {
1049                     try {
1050                         mPhone.mCT.getUtInterface()
1051                             .queryCOLR(obtainMessage(EVENT_SUPP_SVC_QUERY_COMPLETE, this));
1052                     } catch (ImsException e) {
1053                         Rlog.d(LOG_TAG, "processCode: Could not get UT handle for queryCOLR.");
1054                     }
1055                 } else {
1056                     throw new RuntimeException ("Invalid or Unsupported MMI Code");
1057                 }
1058             } else if (mSc != null && (mSc.equals(SC_BS_MT))) {
1059                 try {
1060                     if (isInterrogate()) {
1061                         mPhone.mCT.getUtInterface().queryCallBarring(
1062                                 ImsUtImplBase.CALL_BARRING_SPECIFIC_INCOMING_CALLS,
1063                                 obtainMessage(EVENT_QUERY_ICB_COMPLETE, this));
1064                     } else {
1065                         processIcbMmiCodeForUpdate();
1066                     }
1067                  // TODO: isRegister() case needs to be handled.
1068                 } catch (ImsException e) {
1069                     Rlog.d(LOG_TAG, "processCode: Could not get UT handle for ICB.");
1070                 }
1071             } else if (mSc != null && mSc.equals(SC_BAICa)) {
1072                 int callAction =0;
1073                 // TODO: Should we route through queryCallBarring() here?
1074                 try {
1075                     if (isInterrogate()) {
1076                         mPhone.mCT.getUtInterface().queryCallBarring(
1077                                 ImsUtImplBase.CALL_BARRING_ANONYMOUS_INCOMING,
1078                                 obtainMessage(EVENT_QUERY_ICB_COMPLETE, this));
1079                     } else {
1080                         if (isActivate()) {
1081                             callAction = CommandsInterface.CF_ACTION_ENABLE;
1082                         } else if (isDeactivate()) {
1083                             callAction = CommandsInterface.CF_ACTION_DISABLE;
1084                         }
1085                         mPhone.mCT.getUtInterface()
1086                                 .updateCallBarring(ImsUtImplBase.CALL_BARRING_ANONYMOUS_INCOMING,
1087                                 callAction,
1088                                 obtainMessage(EVENT_SET_COMPLETE,this),
1089                                 null);
1090                     }
1091                 } catch (ImsException e) {
1092                     Rlog.d(LOG_TAG, "processCode: Could not get UT handle for ICBa.");
1093                 }
1094             } else if (mSc != null && mSc.equals(SC_WAIT)) {
1095                 // sia = basic service group
1096                 int serviceClass = siToServiceClass(mSia);
1097 
1098                 if (isActivate() || isDeactivate()) {
1099                     mPhone.setCallWaiting(isActivate(), serviceClass,
1100                             obtainMessage(EVENT_SET_COMPLETE, this));
1101                 } else if (isInterrogate()) {
1102                     mPhone.getCallWaiting(obtainMessage(EVENT_QUERY_COMPLETE, this));
1103                 } else {
1104                     throw new RuntimeException ("Invalid or Unsupported MMI Code");
1105                 }
1106             } else if (mPoundString != null) {
1107                 if (mContext.getResources().getBoolean(
1108                         com.android.internal.R.bool.config_allow_ussd_over_ims)) {
1109                     int ussd_method = getIntCarrierConfig(
1110                                     CarrierConfigManager.KEY_CARRIER_USSD_METHOD_INT);
1111 
1112                     switch (ussd_method) {
1113                         case USSD_OVER_CS_PREFERRED:
1114                             // We'll normally send USSD over the CS pipe, but if it happens that
1115                             // the CS phone is out of service, we'll just try over IMS instead.
1116                             if (mPhone.getDefaultPhone().getServiceStateTracker().mSS.getState()
1117                                     == STATE_IN_SERVICE) {
1118                                 Rlog.i(LOG_TAG, "processCode: Sending ussd string '"
1119                                         + Rlog.pii(LOG_TAG, mPoundString) + "' over CS pipe "
1120                                         + "(allowed over ims).");
1121                                 throw new CallStateException(Phone.CS_FALLBACK);
1122                             } else {
1123                                 Rlog.i(LOG_TAG, "processCode: CS is out of service, "
1124                                         + "sending ussd string '"
1125                                         + Rlog.pii(LOG_TAG, mPoundString) + "' over IMS pipe.");
1126                                 sendUssd(mPoundString);
1127                             }
1128                             break;
1129                         case USSD_OVER_IMS_PREFERRED:
1130                         case USSD_OVER_IMS_ONLY:
1131                             Rlog.i(LOG_TAG, "processCode: Sending ussd string '"
1132                                     + Rlog.pii(LOG_TAG, mPoundString) + "' over IMS pipe.");
1133                             sendUssd(mPoundString);
1134                             break;
1135                         case USSD_OVER_CS_ONLY:
1136                             Rlog.i(LOG_TAG, "processCode: Sending ussd string '"
1137                                     + Rlog.pii(LOG_TAG, mPoundString) + "' over CS pipe.");
1138                             throw new CallStateException(Phone.CS_FALLBACK);
1139                         default:
1140                             Rlog.i(LOG_TAG, "processCode: Sending ussd string '"
1141                                     + Rlog.pii(LOG_TAG, mPoundString) + "' over CS pipe."
1142                                     + "(unsupported method)");
1143                             throw new CallStateException(Phone.CS_FALLBACK);
1144                     }
1145                 } else {
1146                     // USSD codes are not supported over IMS due to modem limitations; send over
1147                     // the CS pipe instead.  This should be fixed in the future.
1148                     Rlog.i(LOG_TAG, "processCode: Sending ussd string '"
1149                             + Rlog.pii(LOG_TAG, mPoundString) + "' over CS pipe.");
1150                     throw new CallStateException(Phone.CS_FALLBACK);
1151                 }
1152             } else {
1153                 Rlog.d(LOG_TAG, "processCode: invalid or unsupported MMI");
1154                 throw new RuntimeException ("Invalid or Unsupported MMI Code");
1155             }
1156         } catch (RuntimeException exc) {
1157             mState = State.FAILED;
1158             mMessage = mContext.getText(com.android.internal.R.string.mmiError);
1159             Rlog.d(LOG_TAG, "processCode: RuntimeException = " + exc);
1160             mPhone.onMMIDone(this);
1161         }
1162     }
1163 
1164     /**
1165      * Called from ImsPhone
1166      *
1167      * An unsolicited USSD NOTIFY or REQUEST has come in matching
1168      * up with this pending USSD request
1169      *
1170      * Note: If REQUEST, this exchange is complete, but the session remains
1171      *       active (ie, the network expects user input).
1172      */
1173     void
onUssdFinished(String ussdMessage, boolean isUssdRequest)1174     onUssdFinished(String ussdMessage, boolean isUssdRequest) {
1175         if (mState == State.PENDING) {
1176             if (TextUtils.isEmpty(ussdMessage)) {
1177                 mMessage = mContext.getText(com.android.internal.R.string.mmiComplete);
1178                 Rlog.v(LOG_TAG, "onUssdFinished: no message; using: " + mMessage);
1179             } else {
1180                 Rlog.v(LOG_TAG, "onUssdFinished: message: " + ussdMessage);
1181                 mMessage = ussdMessage;
1182             }
1183             mIsUssdRequest = isUssdRequest;
1184             // If it's a request, leave it PENDING so that it's cancelable.
1185             if (!isUssdRequest) {
1186                 mState = State.COMPLETE;
1187             }
1188             mPhone.onMMIDone(this);
1189         }
1190     }
1191 
1192     /**
1193      * Called from ImsPhone
1194      *
1195      * The radio has reset, and this is still pending
1196      */
onUssdFinishedError()1197     public void onUssdFinishedError() {
1198         if (mState == State.PENDING) {
1199             mState = State.FAILED;
1200             if (TextUtils.isEmpty(mMessage)) {
1201                 mMessage = mContext.getText(com.android.internal.R.string.mmiError);
1202             }
1203             Rlog.d(LOG_TAG, "onUssdFinishedError: mmi=" + this);
1204             mPhone.onMMIDone(this);
1205         }
1206     }
1207 
sendUssd(String ussdMessage)1208     void sendUssd(String ussdMessage) {
1209         // Treat this as a USSD string
1210         mIsPendingUSSD = true;
1211 
1212         // Note that unlike most everything else, the USSD complete
1213         // response does not complete this MMI code...we wait for
1214         // an unsolicited USSD "Notify" or "Request".
1215         // The matching up of this is done in ImsPhone.
1216 
1217         mPhone.sendUSSD(ussdMessage,
1218             obtainMessage(EVENT_USSD_COMPLETE, this));
1219     }
1220 
1221     /** Called from ImsPhone.handleMessage; not a Handler subclass */
1222     @Override
1223     public void
handleMessage(Message msg)1224     handleMessage (Message msg) {
1225         AsyncResult ar;
1226 
1227         switch (msg.what) {
1228             case EVENT_SET_COMPLETE:
1229                 ar = (AsyncResult) (msg.obj);
1230 
1231                 onSetComplete(msg, ar);
1232                 break;
1233 
1234             case EVENT_SET_CFF_COMPLETE:
1235                 ar = (AsyncResult) (msg.obj);
1236 
1237                 /*
1238                 * msg.arg1 = 1 means to set unconditional voice call forwarding
1239                 * msg.arg2 = 1 means to enable voice call forwarding
1240                 */
1241                 if ((ar.exception == null) && (msg.arg1 == 1)) {
1242                     boolean cffEnabled = (msg.arg2 == 1);
1243                     if (mIccRecords != null) {
1244                         mPhone.setVoiceCallForwardingFlag(mIccRecords,
1245                                 1, cffEnabled, mDialingNumber);
1246                     }
1247                 }
1248 
1249                 onSetComplete(msg, ar);
1250                 break;
1251 
1252             case EVENT_QUERY_CF_COMPLETE:
1253                 ar = (AsyncResult) (msg.obj);
1254                 onQueryCfComplete(ar);
1255                 break;
1256 
1257             case EVENT_QUERY_COMPLETE:
1258                 ar = (AsyncResult) (msg.obj);
1259                 onQueryComplete(ar);
1260                 break;
1261 
1262             case EVENT_USSD_COMPLETE:
1263                 ar = (AsyncResult) (msg.obj);
1264 
1265                 if (ar.exception != null) {
1266                     mState = State.FAILED;
1267                     mMessage = getErrorMessage(ar);
1268 
1269                     mPhone.onMMIDone(this);
1270                 }
1271 
1272                 // Note that unlike most everything else, the USSD complete
1273                 // response does not complete this MMI code...we wait for
1274                 // an unsolicited USSD "Notify" or "Request".
1275                 // The matching up of this is done in ImsPhone.
1276 
1277                 break;
1278 
1279             case EVENT_USSD_CANCEL_COMPLETE:
1280                 mPhone.onMMIDone(this);
1281                 break;
1282 
1283             case EVENT_SUPP_SVC_QUERY_COMPLETE:
1284                 ar = (AsyncResult) (msg.obj);
1285                 onSuppSvcQueryComplete(ar);
1286                 break;
1287 
1288             case EVENT_QUERY_ICB_COMPLETE:
1289                 ar = (AsyncResult) (msg.obj);
1290                 onIcbQueryComplete(ar);
1291                 break;
1292 
1293             case EVENT_GET_CLIR_COMPLETE:
1294                 ar = (AsyncResult) (msg.obj);
1295                 onQueryClirComplete(ar);
1296                 break;
1297 
1298             default:
1299                 break;
1300         }
1301     }
1302 
1303     //***** Private instance methods
1304 
1305     private void
processIcbMmiCodeForUpdate()1306     processIcbMmiCodeForUpdate () {
1307         String dialingNumber = mSia;
1308         String[] icbNum = null;
1309         int callAction;
1310         if (dialingNumber != null) {
1311             icbNum = dialingNumber.split("\\$");
1312         }
1313         callAction = callBarAction(dialingNumber);
1314 
1315         try {
1316             mPhone.mCT.getUtInterface().updateCallBarring(
1317                     ImsUtImplBase.CALL_BARRING_SPECIFIC_INCOMING_CALLS,
1318                     callAction,
1319                     obtainMessage(EVENT_SET_COMPLETE, this),
1320                     icbNum);
1321         } catch (ImsException e) {
1322             Rlog.d(LOG_TAG, "processIcbMmiCodeForUpdate:Could not get UT handle for updating ICB.");
1323         }
1324     }
1325 
1326     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getErrorMessage(AsyncResult ar)1327     private CharSequence getErrorMessage(AsyncResult ar) {
1328         CharSequence errorMessage;
1329         return ((errorMessage = getMmiErrorMessage(ar)) != null) ? errorMessage :
1330                 mContext.getText(com.android.internal.R.string.mmiError);
1331     }
1332 
getMmiErrorMessage(AsyncResult ar)1333     private CharSequence getMmiErrorMessage(AsyncResult ar) {
1334         if (ar.exception instanceof ImsException) {
1335             switch (((ImsException) ar.exception).getCode()) {
1336                 case ImsReasonInfo.CODE_FDN_BLOCKED:
1337                     return mContext.getText(com.android.internal.R.string.mmiFdnError);
1338                 case ImsReasonInfo.CODE_UT_SS_MODIFIED_TO_DIAL:
1339                     return mContext.getText(com.android.internal.R.string.stk_cc_ss_to_dial);
1340                 case ImsReasonInfo.CODE_UT_SS_MODIFIED_TO_USSD:
1341                     return mContext.getText(com.android.internal.R.string.stk_cc_ss_to_ussd);
1342                 case ImsReasonInfo.CODE_UT_SS_MODIFIED_TO_SS:
1343                     return mContext.getText(com.android.internal.R.string.stk_cc_ss_to_ss);
1344                 case ImsReasonInfo.CODE_UT_SS_MODIFIED_TO_DIAL_VIDEO:
1345                     return mContext.getText(com.android.internal.R.string.stk_cc_ss_to_dial_video);
1346                 default:
1347                     return null;
1348             }
1349         } else if (ar.exception instanceof CommandException) {
1350             CommandException err = (CommandException) ar.exception;
1351             if (err.getCommandError() == CommandException.Error.FDN_CHECK_FAILURE) {
1352                 return mContext.getText(com.android.internal.R.string.mmiFdnError);
1353             } else if (err.getCommandError() == CommandException.Error.SS_MODIFIED_TO_DIAL) {
1354                 return mContext.getText(com.android.internal.R.string.stk_cc_ss_to_dial);
1355             } else if (err.getCommandError() == CommandException.Error.SS_MODIFIED_TO_USSD) {
1356                 return mContext.getText(com.android.internal.R.string.stk_cc_ss_to_ussd);
1357             } else if (err.getCommandError() == CommandException.Error.SS_MODIFIED_TO_SS) {
1358                 return mContext.getText(com.android.internal.R.string.stk_cc_ss_to_ss);
1359             } else if (err.getCommandError() == CommandException.Error.SS_MODIFIED_TO_DIAL_VIDEO) {
1360                 return mContext.getText(com.android.internal.R.string.stk_cc_ss_to_dial_video);
1361             } else if (err.getCommandError() == CommandException.Error.INTERNAL_ERR) {
1362                 return mContext.getText(com.android.internal.R.string.mmiError);
1363             }
1364         }
1365         return null;
1366     }
1367 
1368     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getScString()1369     private CharSequence getScString() {
1370         if (mSc != null) {
1371             if (isServiceCodeCallBarring(mSc)) {
1372                 return mContext.getText(com.android.internal.R.string.BaMmi);
1373             } else if (isServiceCodeCallForwarding(mSc)) {
1374                 return mContext.getText(com.android.internal.R.string.CfMmi);
1375             } else if (mSc.equals(SC_PWD)) {
1376                 return mContext.getText(com.android.internal.R.string.PwdMmi);
1377             } else if (mSc.equals(SC_WAIT)) {
1378                 return mContext.getText(com.android.internal.R.string.CwMmi);
1379             } else if (mSc.equals(SC_CLIP)) {
1380                 return mContext.getText(com.android.internal.R.string.ClipMmi);
1381             } else if (mSc.equals(SC_CLIR)) {
1382                 return mContext.getText(com.android.internal.R.string.ClirMmi);
1383             } else if (mSc.equals(SC_COLP)) {
1384                 return mContext.getText(com.android.internal.R.string.ColpMmi);
1385             } else if (mSc.equals(SC_COLR)) {
1386                 return mContext.getText(com.android.internal.R.string.ColrMmi);
1387             } else if (mSc.equals(SC_BS_MT)) {
1388                 return IcbDnMmi;
1389             } else if (mSc.equals(SC_BAICa)) {
1390                 return IcbAnonymousMmi;
1391             }
1392         }
1393 
1394         return "";
1395     }
1396 
1397     private void
onSetComplete(Message msg, AsyncResult ar)1398     onSetComplete(Message msg, AsyncResult ar){
1399         StringBuilder sb = new StringBuilder(getScString());
1400         sb.append("\n");
1401 
1402         if (ar.exception != null) {
1403             mState = State.FAILED;
1404 
1405             if (ar.exception instanceof CommandException) {
1406                 CommandException err = (CommandException) ar.exception;
1407                 CharSequence errorMessage;
1408                 if (err.getCommandError() == CommandException.Error.PASSWORD_INCORRECT) {
1409                     sb.append(mContext.getText(
1410                             com.android.internal.R.string.passwordIncorrect));
1411                 } else if ((errorMessage = getMmiErrorMessage(ar)) != null) {
1412                     sb.append(errorMessage);
1413                 } else if (err.getMessage() != null) {
1414                     sb.append(err.getMessage());
1415                 } else {
1416                     sb.append(mContext.getText(com.android.internal.R.string.mmiError));
1417                 }
1418             } else if (ar.exception instanceof ImsException) {
1419                 sb.append(getImsErrorMessage(ar));
1420             }
1421         } else if (ar.result != null && ar.result instanceof Integer
1422                 && (int) ar.result == CommandsInterface.SS_STATUS_UNKNOWN) {
1423             mState = State.FAILED;
1424             sb = null;
1425         } else if (isActivate()) {
1426             mState = State.COMPLETE;
1427             if (mIsCallFwdReg) {
1428                 sb.append(mContext.getText(
1429                         com.android.internal.R.string.serviceRegistered));
1430             } else {
1431                 sb.append(mContext.getText(
1432                         com.android.internal.R.string.serviceEnabled));
1433             }
1434             // Record CLIR setting
1435             if (mSc.equals(SC_CLIR)) {
1436                 mPhone.saveClirSetting(CommandsInterface.CLIR_INVOCATION);
1437             }
1438         } else if (isDeactivate()) {
1439             mState = State.COMPLETE;
1440             sb.append(mContext.getText(
1441                     com.android.internal.R.string.serviceDisabled));
1442             // Record CLIR setting
1443             if (mSc.equals(SC_CLIR)) {
1444                 mPhone.saveClirSetting(CommandsInterface.CLIR_SUPPRESSION);
1445             }
1446         } else if (isRegister()) {
1447             mState = State.COMPLETE;
1448             sb.append(mContext.getText(
1449                     com.android.internal.R.string.serviceRegistered));
1450         } else if (isErasure()) {
1451             mState = State.COMPLETE;
1452             sb.append(mContext.getText(
1453                     com.android.internal.R.string.serviceErased));
1454         } else {
1455             mState = State.FAILED;
1456             sb.append(mContext.getText(
1457                     com.android.internal.R.string.mmiError));
1458         }
1459 
1460         mMessage = sb;
1461         Rlog.d(LOG_TAG, "onSetComplete: mmi=" + this);
1462         mPhone.onMMIDone(this);
1463     }
1464 
1465     /**
1466      * @param serviceClass 1 bit of the service class bit vectory
1467      * @return String to be used for call forward query MMI response text.
1468      *        Returns null if unrecognized
1469      */
1470 
1471     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
1472     private CharSequence
serviceClassToCFString(int serviceClass)1473     serviceClassToCFString (int serviceClass) {
1474         switch (serviceClass) {
1475             case SERVICE_CLASS_VOICE:
1476                 return mContext.getText(com.android.internal.R.string.serviceClassVoice);
1477             case SERVICE_CLASS_DATA:
1478                 return mContext.getText(com.android.internal.R.string.serviceClassData);
1479             case SERVICE_CLASS_FAX:
1480                 return mContext.getText(com.android.internal.R.string.serviceClassFAX);
1481             case SERVICE_CLASS_SMS:
1482                 return mContext.getText(com.android.internal.R.string.serviceClassSMS);
1483             case SERVICE_CLASS_DATA_SYNC:
1484                 return mContext.getText(com.android.internal.R.string.serviceClassDataSync);
1485             case SERVICE_CLASS_DATA_ASYNC:
1486                 return mContext.getText(com.android.internal.R.string.serviceClassDataAsync);
1487             case SERVICE_CLASS_PACKET:
1488                 return mContext.getText(com.android.internal.R.string.serviceClassPacket);
1489             case SERVICE_CLASS_PAD:
1490                 return mContext.getText(com.android.internal.R.string.serviceClassPAD);
1491             default:
1492                 return null;
1493         }
1494     }
1495 
1496     /** one CallForwardInfo + serviceClassMask -> one line of text */
1497     private CharSequence
makeCFQueryResultMessage(CallForwardInfo info, int serviceClassMask)1498     makeCFQueryResultMessage(CallForwardInfo info, int serviceClassMask) {
1499         CharSequence template;
1500         String sources[] = {"{0}", "{1}", "{2}"};
1501         CharSequence destinations[] = new CharSequence[3];
1502         boolean needTimeTemplate;
1503 
1504         // CF_REASON_NO_REPLY also has a time value associated with
1505         // it. All others don't.
1506 
1507         needTimeTemplate =
1508             (info.reason == CommandsInterface.CF_REASON_NO_REPLY);
1509 
1510         if (info.status == 1) {
1511             if (needTimeTemplate) {
1512                 template = mContext.getText(
1513                         com.android.internal.R.string.cfTemplateForwardedTime);
1514             } else {
1515                 template = mContext.getText(
1516                         com.android.internal.R.string.cfTemplateForwarded);
1517             }
1518         } else if (info.status == 0 && isEmptyOrNull(info.number)) {
1519             template = mContext.getText(
1520                         com.android.internal.R.string.cfTemplateNotForwarded);
1521         } else { /* (info.status == 0) && !isEmptyOrNull(info.number) */
1522             // A call forward record that is not active but contains
1523             // a phone number is considered "registered"
1524 
1525             if (needTimeTemplate) {
1526                 template = mContext.getText(
1527                         com.android.internal.R.string.cfTemplateRegisteredTime);
1528             } else {
1529                 template = mContext.getText(
1530                         com.android.internal.R.string.cfTemplateRegistered);
1531             }
1532         }
1533 
1534         // In the template (from strings.xmls)
1535         //         {0} is one of "bearerServiceCode*"
1536         //        {1} is dialing number
1537         //      {2} is time in seconds
1538 
1539         destinations[0] = serviceClassToCFString(info.serviceClass & serviceClassMask);
1540         destinations[1] = PhoneNumberUtils.stringFromStringAndTOA(info.number, info.toa);
1541         destinations[2] = Integer.toString(info.timeSeconds);
1542 
1543         if (info.reason == CommandsInterface.CF_REASON_UNCONDITIONAL &&
1544                 (info.serviceClass & serviceClassMask)
1545                         == CommandsInterface.SERVICE_CLASS_VOICE) {
1546             boolean cffEnabled = (info.status == 1);
1547             mPhone.setVoiceCallForwardingFlag(mIccRecords, 1, cffEnabled, info.number);
1548         }
1549 
1550         return TextUtils.replace(template, sources, destinations);
1551     }
1552 
1553 
1554     private void
onQueryCfComplete(AsyncResult ar)1555     onQueryCfComplete(AsyncResult ar) {
1556         StringBuilder sb = new StringBuilder(getScString());
1557         sb.append("\n");
1558 
1559         if (ar.exception != null) {
1560             mState = State.FAILED;
1561 
1562             if (ar.exception instanceof ImsException) {
1563                 sb.append(getImsErrorMessage(ar));
1564             }
1565             else {
1566                 sb.append(getErrorMessage(ar));
1567             }
1568         } else if (ar.result instanceof CallForwardInfo[] &&
1569                    ((CallForwardInfo[]) ar.result)[0].status
1570                     == CommandsInterface.SS_STATUS_UNKNOWN) {
1571             sb = null;
1572             mState = State.FAILED;
1573         } else {
1574             CallForwardInfo infos[];
1575 
1576             infos = (CallForwardInfo[]) ar.result;
1577 
1578             if (infos == null || infos.length == 0) {
1579                 // Assume the default is not active
1580                 sb.append(mContext.getText(com.android.internal.R.string.serviceDisabled));
1581 
1582                 // Set unconditional CFF in SIM to false
1583                 mPhone.setVoiceCallForwardingFlag(mIccRecords, 1, false, null);
1584             } else {
1585 
1586                 SpannableStringBuilder tb = new SpannableStringBuilder();
1587 
1588                 // Each bit in the service class gets its own result line
1589                 // The service classes may be split up over multiple
1590                 // CallForwardInfos. So, for each service class, find out
1591                 // which CallForwardInfo represents it and then build
1592                 // the response text based on that
1593 
1594                 for (int serviceClassMask = 1
1595                             ; serviceClassMask <= SERVICE_CLASS_MAX
1596                             ; serviceClassMask <<= 1
1597                 ) {
1598                     for (int i = 0, s = infos.length; i < s ; i++) {
1599                         if ((serviceClassMask & infos[i].serviceClass) != 0) {
1600                             tb.append(makeCFQueryResultMessage(infos[i],
1601                                             serviceClassMask));
1602                             tb.append("\n");
1603                         }
1604                     }
1605                 }
1606                 sb.append(tb);
1607             }
1608 
1609             mState = State.COMPLETE;
1610         }
1611 
1612         mMessage = sb;
1613         Rlog.d(LOG_TAG, "onQueryCfComplete: mmi=" + this);
1614         mPhone.onMMIDone(this);
1615 
1616     }
1617 
onSuppSvcQueryComplete(AsyncResult ar)1618     private void onSuppSvcQueryComplete(AsyncResult ar) {
1619         StringBuilder sb = new StringBuilder(getScString());
1620         sb.append("\n");
1621 
1622         mState = State.FAILED;
1623         if (ar.exception != null) {
1624             if (ar.exception instanceof ImsException) {
1625                 sb.append(getImsErrorMessage(ar));
1626             } else {
1627                 sb.append(getErrorMessage(ar));
1628             }
1629         } else {
1630             if (ar.result instanceof ImsSsInfo) {
1631                 Rlog.d(LOG_TAG, "onSuppSvcQueryComplete: Received CLIP/COLP/COLR Response.");
1632                 // Response for CLIP, COLP and COLR queries.
1633                 ImsSsInfo ssInfo = (ImsSsInfo) ar.result;
1634                 if (ssInfo != null) {
1635                     Rlog.d(LOG_TAG,
1636                             "onSuppSvcQueryComplete: ImsSsInfo mStatus = " + ssInfo.getStatus());
1637                     if (ssInfo.getProvisionStatus() == ImsSsInfo.SERVICE_NOT_PROVISIONED) {
1638                         sb.append(mContext.getText(
1639                                 com.android.internal.R.string.serviceNotProvisioned));
1640                         mState = State.COMPLETE;
1641                     } else if (ssInfo.getStatus() == ImsSsInfo.DISABLED) {
1642                         sb.append(mContext.getText(com.android.internal.R.string.serviceDisabled));
1643                         mState = State.COMPLETE;
1644                     } else if (ssInfo.getStatus() == ImsSsInfo.ENABLED) {
1645                         sb.append(mContext.getText(com.android.internal.R.string.serviceEnabled));
1646                         mState = State.COMPLETE;
1647                     } else {
1648                         sb.append(mContext.getText(com.android.internal.R.string.mmiError));
1649                     }
1650                 } else {
1651                     sb.append(mContext.getText(com.android.internal.R.string.mmiError));
1652                 }
1653 
1654             } else {
1655                 Rlog.d(LOG_TAG,
1656                         "onSuppSvcQueryComplete: Received Call Barring/CSFB CLIP Response.");
1657                 // Response for Call Barring and CSFB CLIP queries.
1658                 int[] infos = (int[]) ar.result;
1659                 if (infos == null || infos.length == 0) {
1660                     sb.append(mContext.getText(com.android.internal.R.string.mmiError));
1661                 } else {
1662                     if (infos[0] != 0) {
1663                         sb.append(mContext.getText(com.android.internal.R.string.serviceEnabled));
1664                         mState = State.COMPLETE;
1665                     } else {
1666                         sb.append(mContext.getText(com.android.internal.R.string.serviceDisabled));
1667                         mState = State.COMPLETE;
1668                     }
1669                 }
1670             }
1671         }
1672 
1673         mMessage = sb;
1674         Rlog.d(LOG_TAG, "onSuppSvcQueryComplete mmi=" + this);
1675         mPhone.onMMIDone(this);
1676     }
1677 
onIcbQueryComplete(AsyncResult ar)1678     private void onIcbQueryComplete(AsyncResult ar) {
1679         Rlog.d(LOG_TAG, "onIcbQueryComplete mmi=" + this);
1680         StringBuilder sb = new StringBuilder(getScString());
1681         sb.append("\n");
1682 
1683         if (ar.exception != null) {
1684             mState = State.FAILED;
1685 
1686             if (ar.exception instanceof ImsException) {
1687                 sb.append(getImsErrorMessage(ar));
1688             } else {
1689                 sb.append(getErrorMessage(ar));
1690             }
1691         } else {
1692             List<ImsSsInfo> infos = null;
1693             try {
1694                 infos = (List<ImsSsInfo>) ar.result;
1695             } catch (ClassCastException cce) {
1696                 // TODO in R: #157# still has ImsSsInfo[] type, fix the type in IImsUtListener.aidl.
1697                 infos = Arrays.asList((ImsSsInfo[]) ar.result);
1698             }
1699             if (infos == null || infos.size() == 0) {
1700                 sb.append(mContext.getText(com.android.internal.R.string.serviceDisabled));
1701             } else {
1702                 ImsSsInfo info;
1703                 for (int i = 0, s = infos.size(); i < s; i++) {
1704                     info = infos.get(i);
1705                     if (info.getIncomingCommunicationBarringNumber() != null) {
1706                         sb.append("Num: " + info.getIncomingCommunicationBarringNumber()
1707                                 + " status: " + info.getStatus() + "\n");
1708                     } else if (info.getStatus() == 1) {
1709                         sb.append(mContext.getText(com.android.internal
1710                                 .R.string.serviceEnabled));
1711                     } else {
1712                         sb.append(mContext.getText(com.android.internal
1713                                 .R.string.serviceDisabled));
1714                     }
1715                 }
1716             }
1717             mState = State.COMPLETE;
1718         }
1719         mMessage = sb;
1720         mPhone.onMMIDone(this);
1721     }
1722 
onQueryClirComplete(AsyncResult ar)1723     private void onQueryClirComplete(AsyncResult ar) {
1724         StringBuilder sb = new StringBuilder(getScString());
1725         sb.append("\n");
1726         mState = State.FAILED;
1727 
1728         if (ar.exception != null) {
1729             if (ar.exception instanceof ImsException) {
1730                 sb.append(getImsErrorMessage(ar));
1731             } else {
1732                 sb.append(getErrorMessage(ar));
1733             }
1734         } else {
1735             int[] clirInfo = (int[]) ar.result;
1736             // ssInfo.getClirOutgoingState() = The 'n' parameter from TS 27.007 7.7
1737             // ssInfo.getClirInterrogationStatus() = The 'm' parameter from TS 27.007 7.7
1738             Rlog.d(LOG_TAG, "onQueryClirComplete: CLIR param n=" + clirInfo[0]
1739                     + " m=" + clirInfo[1]);
1740 
1741             // 'm' parameter.
1742             switch (clirInfo[1]) {
1743                 case ImsSsInfo.CLIR_STATUS_NOT_PROVISIONED:
1744                     sb.append(mContext.getText(
1745                             com.android.internal.R.string.serviceNotProvisioned));
1746                     mState = State.COMPLETE;
1747                     break;
1748                 case ImsSsInfo.CLIR_STATUS_PROVISIONED_PERMANENT:
1749                     sb.append(mContext.getText(
1750                             com.android.internal.R.string.CLIRPermanent));
1751                     mState = State.COMPLETE;
1752                     break;
1753                 case ImsSsInfo.CLIR_STATUS_TEMPORARILY_RESTRICTED:
1754                     // 'n' parameter.
1755                     switch (clirInfo[0]) {
1756                         case ImsSsInfo.CLIR_OUTGOING_DEFAULT:
1757                             sb.append(mContext.getText(
1758                                     com.android.internal.R.string.CLIRDefaultOnNextCallOn));
1759                             mState = State.COMPLETE;
1760                             break;
1761                         case ImsSsInfo.CLIR_OUTGOING_INVOCATION:
1762                             sb.append(mContext.getText(
1763                                     com.android.internal.R.string.CLIRDefaultOnNextCallOn));
1764                             mState = State.COMPLETE;
1765                             break;
1766                         case ImsSsInfo.CLIR_OUTGOING_SUPPRESSION:
1767                             sb.append(mContext.getText(
1768                                     com.android.internal.R.string.CLIRDefaultOnNextCallOff));
1769                             mState = State.COMPLETE;
1770                             break;
1771                         default:
1772                             sb.append(mContext.getText(
1773                                     com.android.internal.R.string.mmiError));
1774                             mState = State.FAILED;
1775                     }
1776                     break;
1777                 case ImsSsInfo.CLIR_STATUS_TEMPORARILY_ALLOWED:
1778                     // 'n' parameter.
1779                     switch (clirInfo[0]) {
1780                         case ImsSsInfo.CLIR_OUTGOING_DEFAULT:
1781                             sb.append(mContext.getText(
1782                                     com.android.internal.R.string.CLIRDefaultOffNextCallOff));
1783                             mState = State.COMPLETE;
1784                             break;
1785                         case ImsSsInfo.CLIR_OUTGOING_INVOCATION:
1786                             sb.append(mContext.getText(
1787                                     com.android.internal.R.string.CLIRDefaultOffNextCallOn));
1788                             mState = State.COMPLETE;
1789                             break;
1790                         case ImsSsInfo.CLIR_OUTGOING_SUPPRESSION:
1791                             sb.append(mContext.getText(
1792                                     com.android.internal.R.string.CLIRDefaultOffNextCallOff));
1793                             mState = State.COMPLETE;
1794                             break;
1795                         default:
1796                             sb.append(mContext.getText(
1797                                     com.android.internal.R.string.mmiError));
1798                             mState = State.FAILED;
1799                     }
1800                     break;
1801                 default:
1802                     sb.append(mContext.getText(
1803                             com.android.internal.R.string.mmiError));
1804                     mState = State.FAILED;
1805             }
1806         }
1807 
1808         mMessage = sb;
1809         Rlog.d(LOG_TAG, "onQueryClirComplete mmi=" + this);
1810         mPhone.onMMIDone(this);
1811     }
1812 
1813     private void
onQueryComplete(AsyncResult ar)1814     onQueryComplete(AsyncResult ar) {
1815         StringBuilder sb = new StringBuilder(getScString());
1816         sb.append("\n");
1817 
1818         mState = State.FAILED;
1819         if (ar.exception != null) {
1820             if (ar.exception instanceof ImsException) {
1821                 sb.append(getImsErrorMessage(ar));
1822             } else {
1823                 sb.append(getErrorMessage(ar));
1824             }
1825         } else if ((ar.result instanceof int[]) &&
1826                    ((int[])ar.result)[0] == CommandsInterface.SS_STATUS_UNKNOWN) {
1827             sb = null;
1828         } else {
1829             int[] ints = (int[])ar.result;
1830 
1831             if (ints != null && ints.length != 0) {
1832                 if (ints[0] == 0) {
1833                     sb.append(mContext.getText(com.android.internal.R.string.serviceDisabled));
1834                     mState = State.COMPLETE;
1835                 } else if (mSc.equals(SC_WAIT)) {
1836                     // Call Waiting includes additional data in the response.
1837                     sb.append(createQueryCallWaitingResultMessage(ints[1]));
1838                     mState = State.COMPLETE;
1839                 } else if (ints[0] == 1) {
1840                     // for all other services, treat it as a boolean
1841                     sb.append(mContext.getText(com.android.internal.R.string.serviceEnabled));
1842                     mState = State.COMPLETE;
1843                 } else {
1844                     sb.append(mContext.getText(com.android.internal.R.string.mmiError));
1845                 }
1846             } else {
1847                 sb.append(mContext.getText(com.android.internal.R.string.mmiError));
1848             }
1849         }
1850 
1851         mMessage = sb;
1852         Rlog.d(LOG_TAG, "onQueryComplete mmi=" + this);
1853         mPhone.onMMIDone(this);
1854     }
1855 
1856     private CharSequence
createQueryCallWaitingResultMessage(int serviceClass)1857     createQueryCallWaitingResultMessage(int serviceClass) {
1858         StringBuilder sb = new StringBuilder(
1859                 mContext.getText(com.android.internal.R.string.serviceEnabledFor));
1860 
1861         for (int classMask = 1
1862                     ; classMask <= SERVICE_CLASS_MAX
1863                     ; classMask <<= 1
1864         ) {
1865             if ((classMask & serviceClass) != 0) {
1866                 sb.append("\n");
1867                 sb.append(serviceClassToCFString(classMask & serviceClass));
1868             }
1869         }
1870         return sb;
1871     }
1872 
getImsErrorMessage(AsyncResult ar)1873     private CharSequence getImsErrorMessage(AsyncResult ar) {
1874         ImsException error = (ImsException) ar.exception;
1875         CharSequence errorMessage;
1876         if ((errorMessage = getMmiErrorMessage(ar)) != null) {
1877             return errorMessage;
1878         } else if (error.getMessage() != null) {
1879             return error.getMessage();
1880         } else {
1881             return getErrorMessage(ar);
1882         }
1883     }
1884 
1885     /**
1886      * Get the int config from carrier config manager.
1887      *
1888      * @param key config key defined in CarrierConfigManager
1889      * @return integer value of corresponding key.
1890      */
getIntCarrierConfig(String key)1891     private int getIntCarrierConfig(String key) {
1892         CarrierConfigManager ConfigManager =
1893                 (CarrierConfigManager) mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
1894         PersistableBundle b = null;
1895         if (ConfigManager != null) {
1896             // If an invalid subId is used, this bundle will contain default values.
1897             b = ConfigManager.getConfigForSubId(mPhone.getSubId());
1898         }
1899         if (b != null) {
1900             return b.getInt(key);
1901         } else {
1902             // Return static default defined in CarrierConfigManager.
1903             return CarrierConfigManager.getDefaultConfig().getInt(key);
1904         }
1905     }
1906 
1907     @Override
getUssdCallbackReceiver()1908     public ResultReceiver getUssdCallbackReceiver() {
1909         return this.mCallbackReceiver;
1910     }
1911 
1912     /**
1913      * Process IMS SS Data received.
1914      */
processImsSsData(AsyncResult data)1915     public void processImsSsData(AsyncResult data) throws ImsException {
1916         try {
1917             ImsSsData ssData = (ImsSsData) data.result;
1918             parseSsData(ssData);
1919         } catch (ClassCastException | NullPointerException ex) {
1920             throw new ImsException("Exception in parsing SS Data", 0);
1921         }
1922     }
1923 
parseSsData(ImsSsData ssData)1924     void parseSsData(ImsSsData ssData) {
1925         ImsException ex = (ssData.getResult() != ImsSsData.RESULT_SUCCESS)
1926                 ? new ImsException(null, ssData.getResult()) : null;
1927         mSc = getScStringFromScType(ssData.getServiceType());
1928         mAction = getActionStringFromReqType(ssData.getRequestType());
1929         Rlog.d(LOG_TAG, "parseSsData msc = " + mSc + ", action = " + mAction + ", ex = " + ex);
1930 
1931         switch (ssData.getRequestType()) {
1932             case ImsSsData.SS_ACTIVATION:
1933             case ImsSsData.SS_DEACTIVATION:
1934             case ImsSsData.SS_REGISTRATION:
1935             case ImsSsData.SS_ERASURE:
1936                 if ((ssData.getResult() == ImsSsData.RESULT_SUCCESS)
1937                         && ssData.isTypeUnConditional()) {
1938                     /*
1939                      * When ssData.serviceType is unconditional (SS_CFU or SS_CF_ALL) and
1940                      * ssData.requestType is activate/register and
1941                      * ServiceClass is Voice/Video/None, turn on voice call forwarding.
1942                      */
1943                     boolean cffEnabled = ((ssData.getRequestType() == ImsSsData.SS_ACTIVATION
1944                             || ssData.getRequestType() == ImsSsData.SS_REGISTRATION)
1945                             && isServiceClassVoiceVideoOrNone(ssData.getServiceClass()));
1946 
1947                     Rlog.d(LOG_TAG, "setCallForwardingFlag cffEnabled: " + cffEnabled);
1948                     if (mIccRecords != null) {
1949                         Rlog.d(LOG_TAG, "setVoiceCallForwardingFlag done from SS Info.");
1950                         //Only CF status is set here as part of activation/registration,
1951                         //number is not available until interrogation.
1952                         mPhone.setVoiceCallForwardingFlag(1, cffEnabled, null);
1953                     } else {
1954                         Rlog.e(LOG_TAG, "setCallForwardingFlag aborted. sim records is null.");
1955                     }
1956                 }
1957                 onSetComplete(null, new AsyncResult(null, ssData.getCallForwardInfo(), ex));
1958                 break;
1959             case ImsSsData.SS_INTERROGATION:
1960                 if (ssData.isTypeClir()) {
1961                     Rlog.d(LOG_TAG, "CLIR INTERROGATION");
1962                     onQueryClirComplete(new AsyncResult(null, ssData.getSuppServiceInfoCompat(), ex));
1963                 } else if (ssData.isTypeCF()) {
1964                     Rlog.d(LOG_TAG, "CALL FORWARD INTERROGATION");
1965                     // Have to translate to an array, since the modem still returns it in the
1966                     // ImsCallForwardInfo[] format.
1967                     List<ImsCallForwardInfo> mCfInfos = ssData.getCallForwardInfo();
1968                     ImsCallForwardInfo[] mCfInfosCompat = null;
1969                     if (mCfInfos != null) {
1970                         mCfInfosCompat = new ImsCallForwardInfo[mCfInfos.size()];
1971                         mCfInfosCompat = mCfInfos.toArray(mCfInfosCompat);
1972                     }
1973                     onQueryCfComplete(new AsyncResult(null, mPhone.handleCfQueryResult(
1974                             mCfInfosCompat), ex));
1975                 } else if (ssData.isTypeBarring()) {
1976                     onSuppSvcQueryComplete(new AsyncResult(null, ssData.getSuppServiceInfoCompat(),
1977                             ex));
1978                 } else if (ssData.isTypeColr() || ssData.isTypeClip() || ssData.isTypeColp()) {
1979                     onSuppSvcQueryComplete(new AsyncResult(null, ssData.getSuppServiceInfo().get(0),
1980                             ex));
1981                 } else if (ssData.isTypeIcb()) {
1982                     onIcbQueryComplete(new AsyncResult(null, ssData.getSuppServiceInfo(), ex));
1983                 } else {
1984                     onQueryComplete(new AsyncResult(null, ssData.getSuppServiceInfoCompat(), ex));
1985                 }
1986                 break;
1987             default:
1988                 Rlog.e(LOG_TAG, "Invaid requestType in SSData : " + ssData.getRequestType());
1989                 break;
1990         }
1991     }
1992 
getScStringFromScType(int serviceType)1993     private String getScStringFromScType(int serviceType) {
1994         switch (serviceType) {
1995             case ImsSsData.SS_CFU:
1996                 return SC_CFU;
1997             case ImsSsData.SS_CF_BUSY:
1998                 return SC_CFB;
1999             case ImsSsData.SS_CF_NO_REPLY:
2000                 return SC_CFNRy;
2001             case ImsSsData.SS_CF_NOT_REACHABLE:
2002                 return SC_CFNR;
2003             case ImsSsData.SS_CF_ALL:
2004                 return SC_CF_All;
2005             case ImsSsData.SS_CF_ALL_CONDITIONAL:
2006                 return SC_CF_All_Conditional;
2007             case ImsSsData.SS_CLIP:
2008                 return SC_CLIP;
2009             case ImsSsData.SS_CLIR:
2010                 return SC_CLIR;
2011             case ImsSsData.SS_COLP:
2012                 return SC_COLP;
2013             case ImsSsData.SS_COLR:
2014                 return SC_COLR;
2015             case ImsSsData.SS_CNAP:
2016                 return SC_CNAP;
2017             case ImsSsData.SS_WAIT:
2018                 return SC_WAIT;
2019             case ImsSsData.SS_BAOC:
2020                 return SC_BAOC;
2021             case ImsSsData.SS_BAOIC:
2022                 return SC_BAOIC;
2023             case ImsSsData.SS_BAOIC_EXC_HOME:
2024                 return SC_BAOICxH;
2025             case ImsSsData.SS_BAIC:
2026                 return SC_BAIC;
2027             case ImsSsData.SS_BAIC_ROAMING:
2028                 return SC_BAICr;
2029             case ImsSsData.SS_ALL_BARRING:
2030                 return SC_BA_ALL;
2031             case ImsSsData.SS_OUTGOING_BARRING:
2032                 return SC_BA_MO;
2033             case ImsSsData.SS_INCOMING_BARRING:
2034                 return SC_BA_MT;
2035             case ImsSsData.SS_INCOMING_BARRING_DN:
2036                 return SC_BS_MT;
2037             case ImsSsData.SS_INCOMING_BARRING_ANONYMOUS:
2038                 return SC_BAICa;
2039             default:
2040                 return null;
2041         }
2042     }
2043 
getActionStringFromReqType(int requestType)2044     private String getActionStringFromReqType(int requestType) {
2045         switch (requestType) {
2046             case ImsSsData.SS_ACTIVATION:
2047                 return ACTION_ACTIVATE;
2048             case ImsSsData.SS_DEACTIVATION:
2049                 return ACTION_DEACTIVATE;
2050             case ImsSsData.SS_INTERROGATION:
2051                 return ACTION_INTERROGATE;
2052             case ImsSsData.SS_REGISTRATION:
2053                 return ACTION_REGISTER;
2054             case ImsSsData.SS_ERASURE:
2055                 return ACTION_ERASURE;
2056             default:
2057                 return null;
2058         }
2059     }
2060 
isServiceClassVoiceVideoOrNone(int serviceClass)2061     private boolean isServiceClassVoiceVideoOrNone(int serviceClass) {
2062         return ((serviceClass == SERVICE_CLASS_NONE) || (serviceClass == SERVICE_CLASS_VOICE)
2063                 || (serviceClass == (SERVICE_CLASS_PACKET + SERVICE_CLASS_DATA_SYNC)));
2064     }
2065 
isSsInfo()2066     public boolean isSsInfo() {
2067         return mIsSsInfo;
2068     }
2069 
setIsSsInfo(boolean isSsInfo)2070     public void setIsSsInfo(boolean isSsInfo) {
2071         mIsSsInfo = isSsInfo;
2072     }
2073 
2074     /***
2075      * TODO: It would be nice to have a method here that can take in a dialstring and
2076      * figure out if there is an MMI code embedded within it.  This code would replace
2077      * some of the string parsing functionality in the Phone App's
2078      * SpecialCharSequenceMgr class.
2079      */
2080 
2081     @Override
toString()2082     public String toString() {
2083         StringBuilder sb = new StringBuilder("ImsPhoneMmiCode {");
2084 
2085         sb.append("State=" + getState());
2086         if (mAction != null) sb.append(" action=" + mAction);
2087         if (mSc != null) sb.append(" sc=" + mSc);
2088         if (mSia != null) sb.append(" sia=" + mSia);
2089         if (mSib != null) sb.append(" sib=" + mSib);
2090         if (mSic != null) sb.append(" sic=" + mSic);
2091         if (mPoundString != null) sb.append(" poundString=" + Rlog.pii(LOG_TAG, mPoundString));
2092         if (mDialingNumber != null) sb.append(" dialingNumber="
2093                 + Rlog.pii(LOG_TAG, mDialingNumber));
2094         if (mPwd != null) sb.append(" pwd=" + Rlog.pii(LOG_TAG, mPwd));
2095         if (mCallbackReceiver != null) sb.append(" hasReceiver");
2096         sb.append("}");
2097         return sb.toString();
2098     }
2099 
2100     @Override
isNetworkInitiatedUssd()2101     public boolean isNetworkInitiatedUssd() {
2102         return mIsNetworkInitiatedUSSD;
2103     }
2104 }
2105