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