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