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
17package com.android.internal.telephony.gsm;
18
19import android.content.Context;
20import com.android.internal.telephony.*;
21
22import android.os.*;
23import android.telephony.PhoneNumberUtils;
24import android.text.SpannableStringBuilder;
25import android.text.TextUtils;
26import android.util.Log;
27
28import static com.android.internal.telephony.CommandsInterface.*;
29
30import java.util.regex.Pattern;
31import java.util.regex.Matcher;
32
33/**
34 * The motto for this file is:
35 *
36 * "NOTE:    By using the # as a separator, most cases are expected to be unambiguous."
37 *   -- TS 22.030 6.5.2
38 *
39 * {@hide}
40 *
41 */
42public final class GsmMmiCode  extends Handler implements MmiCode {
43    static final String LOG_TAG = "GSM";
44
45    //***** Constants
46
47    // From TS 22.030 6.5.2
48    static final String ACTION_ACTIVATE = "*";
49    static final String ACTION_DEACTIVATE = "#";
50    static final String ACTION_INTERROGATE = "*#";
51    static final String ACTION_REGISTER = "**";
52    static final String ACTION_ERASURE = "##";
53
54    // Supp Service cocdes from TS 22.030 Annex B
55
56    //Called line presentation
57    static final String SC_CLIP    = "30";
58    static final String SC_CLIR    = "31";
59
60    // Call Forwarding
61    static final String SC_CFU     = "21";
62    static final String SC_CFB     = "67";
63    static final String SC_CFNRy   = "61";
64    static final String SC_CFNR    = "62";
65
66    static final String SC_CF_All = "002";
67    static final String SC_CF_All_Conditional = "004";
68
69    // Call Waiting
70    static final String SC_WAIT     = "43";
71
72    // Call Barring
73    static final String SC_BAOC         = "33";
74    static final String SC_BAOIC        = "331";
75    static final String SC_BAOICxH      = "332";
76    static final String SC_BAIC         = "35";
77    static final String SC_BAICr        = "351";
78
79    static final String SC_BA_ALL       = "330";
80    static final String SC_BA_MO        = "333";
81    static final String SC_BA_MT        = "353";
82
83    // Supp Service Password registration
84    static final String SC_PWD          = "03";
85
86    // PIN/PIN2/PUK/PUK2
87    static final String SC_PIN          = "04";
88    static final String SC_PIN2         = "042";
89    static final String SC_PUK          = "05";
90    static final String SC_PUK2         = "052";
91
92    //***** Event Constants
93
94    static final int EVENT_SET_COMPLETE         = 1;
95    static final int EVENT_GET_CLIR_COMPLETE    = 2;
96    static final int EVENT_QUERY_CF_COMPLETE    = 3;
97    static final int EVENT_USSD_COMPLETE        = 4;
98    static final int EVENT_QUERY_COMPLETE       = 5;
99    static final int EVENT_SET_CFF_COMPLETE     = 6;
100    static final int EVENT_USSD_CANCEL_COMPLETE = 7;
101
102    //***** Instance Variables
103
104    GSMPhone phone;
105    Context context;
106
107    String action;              // One of ACTION_*
108    String sc;                  // Service Code
109    String sia, sib, sic;       // Service Info a,b,c
110    String poundString;         // Entire MMI string up to and including #
111    String dialingNumber;
112    String pwd;                 // For password registration
113
114    /** Set to true in processCode, not at newFromDialString time */
115    private boolean isPendingUSSD;
116
117    private boolean isUssdRequest;
118
119    State state = State.PENDING;
120    CharSequence message;
121
122    //***** Class Variables
123
124
125    // See TS 22.030 6.5.2 "Structure of the MMI"
126
127    static Pattern sPatternSuppService = Pattern.compile(
128        "((\\*|#|\\*#|\\*\\*|##)(\\d{2,3})(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*))?)?)?)?#)(.*)");
129/*       1  2                    3          4  5       6   7         8    9     10  11             12
130
131         1 = Full string up to and including #
132         2 = action (activation/interrogation/registration/erasure)
133         3 = service code
134         5 = SIA
135         7 = SIB
136         9 = SIC
137         10 = dialing number
138*/
139
140    static final int MATCH_GROUP_POUND_STRING = 1;
141
142    static final int MATCH_GROUP_ACTION = 2;
143                        //(activation/interrogation/registration/erasure)
144
145    static final int MATCH_GROUP_SERVICE_CODE = 3;
146    static final int MATCH_GROUP_SIA = 5;
147    static final int MATCH_GROUP_SIB = 7;
148    static final int MATCH_GROUP_SIC = 9;
149    static final int MATCH_GROUP_PWD_CONFIRM = 11;
150    static final int MATCH_GROUP_DIALING_NUMBER = 12;
151
152
153    //***** Public Class methods
154
155    /**
156     * Some dial strings in GSM are defined to do non-call setup
157     * things, such as modify or query supplementry service settings (eg, call
158     * forwarding). These are generally referred to as "MMI codes".
159     * We look to see if the dial string contains a valid MMI code (potentially
160     * with a dial string at the end as well) and return info here.
161     *
162     * If the dial string contains no MMI code, we return an instance with
163     * only "dialingNumber" set
164     *
165     * Please see flow chart in TS 22.030 6.5.3.2
166     */
167
168    static GsmMmiCode
169    newFromDialString(String dialString, GSMPhone phone) {
170        Matcher m;
171        GsmMmiCode ret = null;
172
173        m = sPatternSuppService.matcher(dialString);
174
175        // Is this formatted like a standard supplementary service code?
176        if (m.matches()) {
177            ret = new GsmMmiCode(phone);
178            ret.poundString = makeEmptyNull(m.group(MATCH_GROUP_POUND_STRING));
179            ret.action = makeEmptyNull(m.group(MATCH_GROUP_ACTION));
180            ret.sc = makeEmptyNull(m.group(MATCH_GROUP_SERVICE_CODE));
181            ret.sia = makeEmptyNull(m.group(MATCH_GROUP_SIA));
182            ret.sib = makeEmptyNull(m.group(MATCH_GROUP_SIB));
183            ret.sic = makeEmptyNull(m.group(MATCH_GROUP_SIC));
184            ret.pwd = makeEmptyNull(m.group(MATCH_GROUP_PWD_CONFIRM));
185            ret.dialingNumber = makeEmptyNull(m.group(MATCH_GROUP_DIALING_NUMBER));
186
187        } else if (dialString.endsWith("#")) {
188            // TS 22.030 sec 6.5.3.2
189            // "Entry of any characters defined in the 3GPP TS 23.038 [8] Default Alphabet
190            // (up to the maximum defined in 3GPP TS 24.080 [10]), followed by #SEND".
191
192            ret = new GsmMmiCode(phone);
193            ret.poundString = dialString;
194        } else if (isShortCode(dialString, phone)) {
195            // this may be a short code, as defined in TS 22.030, 6.5.3.2
196            ret = new GsmMmiCode(phone);
197            ret.dialingNumber = dialString;
198        }
199
200        return ret;
201    }
202
203    static GsmMmiCode
204    newNetworkInitiatedUssd (String ussdMessage,
205                                boolean isUssdRequest, GSMPhone phone) {
206        GsmMmiCode ret;
207
208        ret = new GsmMmiCode(phone);
209
210        ret.message = ussdMessage;
211        ret.isUssdRequest = isUssdRequest;
212
213        // If it's a request, set to PENDING so that it's cancelable.
214        if (isUssdRequest) {
215            ret.isPendingUSSD = true;
216            ret.state = State.PENDING;
217        } else {
218            ret.state = State.COMPLETE;
219        }
220
221        return ret;
222    }
223
224    static GsmMmiCode newFromUssdUserInput(String ussdMessge, GSMPhone phone) {
225        GsmMmiCode ret = new GsmMmiCode(phone);
226
227        ret.message = ussdMessge;
228        ret.state = State.PENDING;
229        ret.isPendingUSSD = true;
230
231        return ret;
232    }
233
234    //***** Private Class methods
235
236    /** make empty strings be null.
237     *  Regexp returns empty strings for empty groups
238     */
239    private static String
240    makeEmptyNull (String s) {
241        if (s != null && s.length() == 0) return null;
242
243        return s;
244    }
245
246    /** returns true of the string is empty or null */
247    private static boolean
248    isEmptyOrNull(CharSequence s) {
249        return s == null || (s.length() == 0);
250    }
251
252
253    private static int
254    scToCallForwardReason(String sc) {
255        if (sc == null) {
256            throw new RuntimeException ("invalid call forward sc");
257        }
258
259        if (sc.equals(SC_CF_All)) {
260           return CommandsInterface.CF_REASON_ALL;
261        } else if (sc.equals(SC_CFU)) {
262            return CommandsInterface.CF_REASON_UNCONDITIONAL;
263        } else if (sc.equals(SC_CFB)) {
264            return CommandsInterface.CF_REASON_BUSY;
265        } else if (sc.equals(SC_CFNR)) {
266            return CommandsInterface.CF_REASON_NOT_REACHABLE;
267        } else if (sc.equals(SC_CFNRy)) {
268            return CommandsInterface.CF_REASON_NO_REPLY;
269        } else if (sc.equals(SC_CF_All_Conditional)) {
270           return CommandsInterface.CF_REASON_ALL_CONDITIONAL;
271        } else {
272            throw new RuntimeException ("invalid call forward sc");
273        }
274    }
275
276    private static int
277    siToServiceClass(String si) {
278        if (si == null || si.length() == 0) {
279                return  SERVICE_CLASS_NONE;
280        } else {
281            // NumberFormatException should cause MMI fail
282            int serviceCode = Integer.parseInt(si, 10);
283
284            switch (serviceCode) {
285                case 10: return SERVICE_CLASS_SMS + SERVICE_CLASS_FAX  + SERVICE_CLASS_VOICE;
286                case 11: return SERVICE_CLASS_VOICE;
287                case 12: return SERVICE_CLASS_SMS + SERVICE_CLASS_FAX;
288                case 13: return SERVICE_CLASS_FAX;
289
290                case 16: return SERVICE_CLASS_SMS;
291
292                case 19: return SERVICE_CLASS_FAX + SERVICE_CLASS_VOICE;
293/*
294    Note for code 20:
295     From TS 22.030 Annex C:
296                "All GPRS bearer services" are not included in "All tele and bearer services"
297                    and "All bearer services"."
298....so SERVICE_CLASS_DATA, which (according to 27.007) includes GPRS
299*/
300                case 20: return SERVICE_CLASS_DATA_ASYNC + SERVICE_CLASS_DATA_SYNC;
301
302                case 21: return SERVICE_CLASS_PAD + SERVICE_CLASS_DATA_ASYNC;
303                case 22: return SERVICE_CLASS_PACKET + SERVICE_CLASS_DATA_SYNC;
304                case 24: return SERVICE_CLASS_DATA_SYNC;
305                case 25: return SERVICE_CLASS_DATA_ASYNC;
306                case 26: return SERVICE_CLASS_DATA_SYNC + SERVICE_CLASS_VOICE;
307                case 99: return SERVICE_CLASS_PACKET;
308
309                default:
310                    throw new RuntimeException("unsupported MMI service code " + si);
311            }
312        }
313    }
314
315    private static int
316    siToTime (String si) {
317        if (si == null || si.length() == 0) {
318            return 0;
319        } else {
320            // NumberFormatException should cause MMI fail
321            return Integer.parseInt(si, 10);
322        }
323    }
324
325    static boolean
326    isServiceCodeCallForwarding(String sc) {
327        return sc != null &&
328                (sc.equals(SC_CFU)
329                || sc.equals(SC_CFB) || sc.equals(SC_CFNRy)
330                || sc.equals(SC_CFNR) || sc.equals(SC_CF_All)
331                || sc.equals(SC_CF_All_Conditional));
332    }
333
334    static boolean
335    isServiceCodeCallBarring(String sc) {
336        return sc != null &&
337                (sc.equals(SC_BAOC)
338                || sc.equals(SC_BAOIC)
339                || sc.equals(SC_BAOICxH)
340                || sc.equals(SC_BAIC)
341                || sc.equals(SC_BAICr)
342                || sc.equals(SC_BA_ALL)
343                || sc.equals(SC_BA_MO)
344                || sc.equals(SC_BA_MT));
345    }
346
347    static String
348    scToBarringFacility(String sc) {
349        if (sc == null) {
350            throw new RuntimeException ("invalid call barring sc");
351        }
352
353        if (sc.equals(SC_BAOC)) {
354            return CommandsInterface.CB_FACILITY_BAOC;
355        } else if (sc.equals(SC_BAOIC)) {
356            return CommandsInterface.CB_FACILITY_BAOIC;
357        } else if (sc.equals(SC_BAOICxH)) {
358            return CommandsInterface.CB_FACILITY_BAOICxH;
359        } else if (sc.equals(SC_BAIC)) {
360            return CommandsInterface.CB_FACILITY_BAIC;
361        } else if (sc.equals(SC_BAICr)) {
362            return CommandsInterface.CB_FACILITY_BAICr;
363        } else if (sc.equals(SC_BA_ALL)) {
364            return CommandsInterface.CB_FACILITY_BA_ALL;
365        } else if (sc.equals(SC_BA_MO)) {
366            return CommandsInterface.CB_FACILITY_BA_MO;
367        } else if (sc.equals(SC_BA_MT)) {
368            return CommandsInterface.CB_FACILITY_BA_MT;
369        } else {
370            throw new RuntimeException ("invalid call barring sc");
371        }
372    }
373
374    //***** Constructor
375
376    GsmMmiCode (GSMPhone phone) {
377        // The telephony unit-test cases may create GsmMmiCode's
378        // in secondary threads
379        super(phone.getHandler().getLooper());
380        this.phone = phone;
381        this.context = phone.getContext();
382    }
383
384    //***** MmiCode implementation
385
386    public State
387    getState() {
388        return state;
389    }
390
391    public CharSequence
392    getMessage() {
393        return message;
394    }
395
396    // inherited javadoc suffices
397    public void
398    cancel() {
399        // Complete or failed cannot be cancelled
400        if (state == State.COMPLETE || state == State.FAILED) {
401            return;
402        }
403
404        state = State.CANCELLED;
405
406        if (isPendingUSSD) {
407            /*
408             * There can only be one pending USSD session, so tell the radio to
409             * cancel it.
410             */
411            phone.mCM.cancelPendingUssd(obtainMessage(EVENT_USSD_CANCEL_COMPLETE, this));
412
413            /*
414             * Don't call phone.onMMIDone here; wait for CANCEL_COMPLETE notice
415             * from RIL.
416             */
417        } else {
418            // TODO in cases other than USSD, it would be nice to cancel
419            // the pending radio operation. This requires RIL cancellation
420            // support, which does not presently exist.
421
422            phone.onMMIDone (this);
423        }
424
425    }
426
427    public boolean isCancelable() {
428        /* Can only cancel pending USSD sessions. */
429        return isPendingUSSD;
430    }
431
432    //***** Instance Methods
433
434    /** Does this dial string contain a structured or unstructured MMI code? */
435    boolean
436    isMMI() {
437        return poundString != null;
438    }
439
440    /* Is this a 1 or 2 digit "short code" as defined in TS 22.030 sec 6.5.3.2? */
441    boolean
442    isShortCode() {
443        return poundString == null
444                    && dialingNumber != null && dialingNumber.length() <= 2;
445
446    }
447
448    /**
449     * Helper function for newFromDialString.  Returns true if dialString appears to be a short code
450     * AND conditions are correct for it to be treated as such.
451     */
452    static private boolean isShortCode(String dialString, GSMPhone phone) {
453        // Refer to TS 22.030 Figure 3.5.3.2:
454        // A 1 or 2 digit "short code" is treated as USSD if it is entered while on a call or
455        // does not satisfy the condition (exactly 2 digits && starts with '1').
456        return ((dialString != null && dialString.length() <= 2)
457                && !PhoneNumberUtils.isEmergencyNumber(dialString)
458                && (phone.isInCall()
459                    || !((dialString.length() == 2 && dialString.charAt(0) == '1')
460                         /* While contrary to TS 22.030, there is strong precendence
461                          * for treating "0" and "00" as call setup strings.
462                          */
463                         || dialString.equals("0")
464                         || dialString.equals("00"))));
465    }
466    /**
467     * @return true if the Service Code is PIN/PIN2/PUK/PUK2-related
468     */
469    boolean isPinCommand() {
470        return sc != null && (sc.equals(SC_PIN) || sc.equals(SC_PIN2)
471                              || sc.equals(SC_PUK) || sc.equals(SC_PUK2));
472     }
473
474    /**
475     * *See TS 22.030 Annex B
476     * In temporary mode, to suppress CLIR for a single call, enter:
477     *      " * 31 # <called number> SEND "
478     *  In temporary mode, to invoke CLIR for a single call enter:
479     *       " # 31 # <called number> SEND "
480     */
481
482    boolean
483    isTemporaryModeCLIR() {
484        return sc != null && sc.equals(SC_CLIR) && dialingNumber != null
485                && (isActivate() || isDeactivate());
486    }
487
488    /**
489     * returns CommandsInterface.CLIR_*
490     * See also isTemporaryModeCLIR()
491     */
492    int
493    getCLIRMode() {
494        if (sc != null && sc.equals(SC_CLIR)) {
495            if (isActivate()) {
496                return CommandsInterface.CLIR_SUPPRESSION;
497            } else if (isDeactivate()) {
498                return CommandsInterface.CLIR_INVOCATION;
499            }
500        }
501
502        return CommandsInterface.CLIR_DEFAULT;
503    }
504
505    boolean isActivate() {
506        return action != null && action.equals(ACTION_ACTIVATE);
507    }
508
509    boolean isDeactivate() {
510        return action != null && action.equals(ACTION_DEACTIVATE);
511    }
512
513    boolean isInterrogate() {
514        return action != null && action.equals(ACTION_INTERROGATE);
515    }
516
517    boolean isRegister() {
518        return action != null && action.equals(ACTION_REGISTER);
519    }
520
521    boolean isErasure() {
522        return action != null && action.equals(ACTION_ERASURE);
523    }
524
525    /**
526     * Returns true if this is a USSD code that's been submitted to the
527     * network...eg, after processCode() is called
528     */
529    public boolean isPendingUSSD() {
530        return isPendingUSSD;
531    }
532
533    public boolean isUssdRequest() {
534        return isUssdRequest;
535    }
536
537    /** Process a MMI code or short code...anything that isn't a dialing number */
538    void
539    processCode () {
540        try {
541            if (isShortCode()) {
542                Log.d(LOG_TAG, "isShortCode");
543                // These just get treated as USSD.
544                sendUssd(dialingNumber);
545            } else if (dialingNumber != null) {
546                // We should have no dialing numbers here
547                throw new RuntimeException ("Invalid or Unsupported MMI Code");
548            } else if (sc != null && sc.equals(SC_CLIP)) {
549                Log.d(LOG_TAG, "is CLIP");
550                if (isInterrogate()) {
551                    phone.mCM.queryCLIP(
552                            obtainMessage(EVENT_QUERY_COMPLETE, this));
553                } else {
554                    throw new RuntimeException ("Invalid or Unsupported MMI Code");
555                }
556            } else if (sc != null && sc.equals(SC_CLIR)) {
557                Log.d(LOG_TAG, "is CLIR");
558                if (isActivate()) {
559                    phone.mCM.setCLIR(CommandsInterface.CLIR_INVOCATION,
560                        obtainMessage(EVENT_SET_COMPLETE, this));
561                } else if (isDeactivate()) {
562                    phone.mCM.setCLIR(CommandsInterface.CLIR_SUPPRESSION,
563                        obtainMessage(EVENT_SET_COMPLETE, this));
564                } else if (isInterrogate()) {
565                    phone.mCM.getCLIR(
566                        obtainMessage(EVENT_GET_CLIR_COMPLETE, this));
567                } else {
568                    throw new RuntimeException ("Invalid or Unsupported MMI Code");
569                }
570            } else if (isServiceCodeCallForwarding(sc)) {
571                Log.d(LOG_TAG, "is CF");
572
573                String dialingNumber = sia;
574                int serviceClass = siToServiceClass(sib);
575                int reason = scToCallForwardReason(sc);
576                int time = siToTime(sic);
577
578                if (isInterrogate()) {
579                    phone.mCM.queryCallForwardStatus(
580                            reason, serviceClass,  dialingNumber,
581                                obtainMessage(EVENT_QUERY_CF_COMPLETE, this));
582                } else {
583                    int cfAction;
584
585                    if (isActivate()) {
586                        cfAction = CommandsInterface.CF_ACTION_ENABLE;
587                    } else if (isDeactivate()) {
588                        cfAction = CommandsInterface.CF_ACTION_DISABLE;
589                    } else if (isRegister()) {
590                        cfAction = CommandsInterface.CF_ACTION_REGISTRATION;
591                    } else if (isErasure()) {
592                        cfAction = CommandsInterface.CF_ACTION_ERASURE;
593                    } else {
594                        throw new RuntimeException ("invalid action");
595                    }
596
597                    int isSettingUnconditionalVoice =
598                        (((reason == CommandsInterface.CF_REASON_UNCONDITIONAL) ||
599                                (reason == CommandsInterface.CF_REASON_ALL)) &&
600                                (((serviceClass & CommandsInterface.SERVICE_CLASS_VOICE) != 0) ||
601                                 (serviceClass == CommandsInterface.SERVICE_CLASS_NONE))) ? 1 : 0;
602
603                    int isEnableDesired =
604                        ((cfAction == CommandsInterface.CF_ACTION_ENABLE) ||
605                                (cfAction == CommandsInterface.CF_ACTION_REGISTRATION)) ? 1 : 0;
606
607                    Log.d(LOG_TAG, "is CF setCallForward");
608                    phone.mCM.setCallForward(cfAction, reason, serviceClass,
609                            dialingNumber, time, obtainMessage(
610                                    EVENT_SET_CFF_COMPLETE,
611                                    isSettingUnconditionalVoice,
612                                    isEnableDesired, this));
613                }
614            } else if (isServiceCodeCallBarring(sc)) {
615                // sia = password
616                // sib = basic service group
617
618                String password = sia;
619                int serviceClass = siToServiceClass(sib);
620                String facility = scToBarringFacility(sc);
621
622                if (isInterrogate()) {
623                    phone.mCM.queryFacilityLock(facility, password,
624                            serviceClass, obtainMessage(EVENT_QUERY_COMPLETE, this));
625                } else if (isActivate() || isDeactivate()) {
626                    phone.mCM.setFacilityLock(facility, isActivate(), password,
627                            serviceClass, obtainMessage(EVENT_SET_COMPLETE, this));
628                } else {
629                    throw new RuntimeException ("Invalid or Unsupported MMI Code");
630                }
631
632            } else if (sc != null && sc.equals(SC_PWD)) {
633                // sia = fac
634                // sib = old pwd
635                // sic = new pwd
636                // pwd = new pwd
637                String facility;
638                String oldPwd = sib;
639                String newPwd = sic;
640                if (isActivate() || isRegister()) {
641                    // Even though ACTIVATE is acceptable, this is really termed a REGISTER
642                    action = ACTION_REGISTER;
643
644                    if (sia == null) {
645                        // If sc was not specified, treat it as BA_ALL.
646                        facility = CommandsInterface.CB_FACILITY_BA_ALL;
647                    } else {
648                        facility = scToBarringFacility(sia);
649                    }
650                    if (newPwd.equals(pwd)) {
651                        phone.mCM.changeBarringPassword(facility, oldPwd,
652                                newPwd, obtainMessage(EVENT_SET_COMPLETE, this));
653                    } else {
654                        // password mismatch; return error
655                        handlePasswordError(com.android.internal.R.string.passwordIncorrect);
656                    }
657                } else {
658                    throw new RuntimeException ("Invalid or Unsupported MMI Code");
659                }
660
661            } else if (sc != null && sc.equals(SC_WAIT)) {
662                // sia = basic service group
663                int serviceClass = siToServiceClass(sia);
664
665                if (isActivate() || isDeactivate()) {
666                    phone.mCM.setCallWaiting(isActivate(), serviceClass,
667                            obtainMessage(EVENT_SET_COMPLETE, this));
668                } else if (isInterrogate()) {
669                    phone.mCM.queryCallWaiting(serviceClass,
670                            obtainMessage(EVENT_QUERY_COMPLETE, this));
671                } else {
672                    throw new RuntimeException ("Invalid or Unsupported MMI Code");
673                }
674            } else if (isPinCommand()) {
675                // sia = old PIN or PUK
676                // sib = new PIN
677                // sic = new PIN
678                String oldPinOrPuk = sia;
679                String newPin = sib;
680                int pinLen = newPin.length();
681                if (isRegister()) {
682                    if (!newPin.equals(sic)) {
683                        // password mismatch; return error
684                        handlePasswordError(com.android.internal.R.string.mismatchPin);
685                    } else if (pinLen < 4 || pinLen > 8 ) {
686                        // invalid length
687                        handlePasswordError(com.android.internal.R.string.invalidPin);
688                    } else if (sc.equals(SC_PIN) &&
689                               phone.mSimCard.getState() == SimCard.State.PUK_REQUIRED ) {
690                        // Sim is puk-locked
691                        handlePasswordError(com.android.internal.R.string.needPuk);
692                    } else {
693                        // pre-checks OK
694                        if (sc.equals(SC_PIN)) {
695                            phone.mCM.changeIccPin(oldPinOrPuk, newPin,
696                                    obtainMessage(EVENT_SET_COMPLETE, this));
697                        } else if (sc.equals(SC_PIN2)) {
698                            phone.mCM.changeIccPin2(oldPinOrPuk, newPin,
699                                    obtainMessage(EVENT_SET_COMPLETE, this));
700                        } else if (sc.equals(SC_PUK)) {
701                            phone.mCM.supplyIccPuk(oldPinOrPuk, newPin,
702                                    obtainMessage(EVENT_SET_COMPLETE, this));
703                        } else if (sc.equals(SC_PUK2)) {
704                            phone.mCM.supplyIccPuk2(oldPinOrPuk, newPin,
705                                    obtainMessage(EVENT_SET_COMPLETE, this));
706                        }
707                    }
708                } else {
709                    throw new RuntimeException ("Invalid or Unsupported MMI Code");
710                }
711            } else if (poundString != null) {
712                sendUssd(poundString);
713            } else {
714                throw new RuntimeException ("Invalid or Unsupported MMI Code");
715            }
716        } catch (RuntimeException exc) {
717            state = State.FAILED;
718            message = context.getText(com.android.internal.R.string.mmiError);
719            phone.onMMIDone(this);
720        }
721    }
722
723    private void handlePasswordError(int res) {
724        state = State.FAILED;
725        StringBuilder sb = new StringBuilder(getScString());
726        sb.append("\n");
727        sb.append(context.getText(res));
728        message = sb;
729        phone.onMMIDone(this);
730    }
731
732    /**
733     * Called from GSMPhone
734     *
735     * An unsolicited USSD NOTIFY or REQUEST has come in matching
736     * up with this pending USSD request
737     *
738     * Note: If REQUEST, this exchange is complete, but the session remains
739     *       active (ie, the network expects user input).
740     */
741    void
742    onUssdFinished(String ussdMessage, boolean isUssdRequest) {
743        if (state == State.PENDING) {
744            if (ussdMessage == null) {
745                message = context.getText(com.android.internal.R.string.mmiComplete);
746            } else {
747                message = ussdMessage;
748            }
749            this.isUssdRequest = isUssdRequest;
750            // If it's a request, leave it PENDING so that it's cancelable.
751            if (!isUssdRequest) {
752                state = State.COMPLETE;
753            }
754
755            phone.onMMIDone(this);
756        }
757    }
758
759    /**
760     * Called from GSMPhone
761     *
762     * The radio has reset, and this is still pending
763     */
764
765    void
766    onUssdFinishedError() {
767        if (state == State.PENDING) {
768            state = State.FAILED;
769            message = context.getText(com.android.internal.R.string.mmiError);
770
771            phone.onMMIDone(this);
772        }
773    }
774
775    void sendUssd(String ussdMessage) {
776        // Treat this as a USSD string
777        isPendingUSSD = true;
778
779        // Note that unlike most everything else, the USSD complete
780        // response does not complete this MMI code...we wait for
781        // an unsolicited USSD "Notify" or "Request".
782        // The matching up of this is doene in GSMPhone.
783
784        phone.mCM.sendUSSD(ussdMessage,
785            obtainMessage(EVENT_USSD_COMPLETE, this));
786    }
787
788    /** Called from GSMPhone.handleMessage; not a Handler subclass */
789    public void
790    handleMessage (Message msg) {
791        AsyncResult ar;
792
793        switch (msg.what) {
794            case EVENT_SET_COMPLETE:
795                ar = (AsyncResult) (msg.obj);
796
797                onSetComplete(ar);
798                break;
799
800            case EVENT_SET_CFF_COMPLETE:
801                ar = (AsyncResult) (msg.obj);
802
803                /*
804                * msg.arg1 = 1 means to set unconditional voice call forwarding
805                * msg.arg2 = 1 means to enable voice call forwarding
806                */
807                if ((ar.exception == null) && (msg.arg1 == 1)) {
808                    boolean cffEnabled = (msg.arg2 == 1);
809                    phone.mSIMRecords.setVoiceCallForwardingFlag(1, cffEnabled);
810                }
811
812                onSetComplete(ar);
813                break;
814
815            case EVENT_GET_CLIR_COMPLETE:
816                ar = (AsyncResult) (msg.obj);
817                onGetClirComplete(ar);
818            break;
819
820            case EVENT_QUERY_CF_COMPLETE:
821                ar = (AsyncResult) (msg.obj);
822                onQueryCfComplete(ar);
823            break;
824
825            case EVENT_QUERY_COMPLETE:
826                ar = (AsyncResult) (msg.obj);
827                onQueryComplete(ar);
828            break;
829
830            case EVENT_USSD_COMPLETE:
831                ar = (AsyncResult) (msg.obj);
832
833                if (ar.exception != null) {
834                    state = State.FAILED;
835                    message = context.getText(
836                                            com.android.internal.R.string.mmiError);
837
838                    phone.onMMIDone(this);
839                }
840
841                // Note that unlike most everything else, the USSD complete
842                // response does not complete this MMI code...we wait for
843                // an unsolicited USSD "Notify" or "Request".
844                // The matching up of this is done in GSMPhone.
845
846            break;
847
848            case EVENT_USSD_CANCEL_COMPLETE:
849                phone.onMMIDone(this);
850            break;
851        }
852    }
853    //***** Private instance methods
854
855    private CharSequence getScString() {
856        if (sc != null) {
857            if (isServiceCodeCallBarring(sc)) {
858                return context.getText(com.android.internal.R.string.BaMmi);
859            } else if (isServiceCodeCallForwarding(sc)) {
860                return context.getText(com.android.internal.R.string.CfMmi);
861            } else if (sc.equals(SC_CLIP)) {
862                return context.getText(com.android.internal.R.string.ClipMmi);
863            } else if (sc.equals(SC_CLIR)) {
864                return context.getText(com.android.internal.R.string.ClirMmi);
865            } else if (sc.equals(SC_PWD)) {
866                return context.getText(com.android.internal.R.string.PwdMmi);
867            } else if (sc.equals(SC_WAIT)) {
868                return context.getText(com.android.internal.R.string.CwMmi);
869            } else if (isPinCommand()) {
870                return context.getText(com.android.internal.R.string.PinMmi);
871            }
872        }
873
874        return "";
875    }
876
877    private void
878    onSetComplete(AsyncResult ar){
879        StringBuilder sb = new StringBuilder(getScString());
880        sb.append("\n");
881
882        if (ar.exception != null) {
883            state = State.FAILED;
884            if (ar.exception instanceof CommandException) {
885                CommandException.Error err = ((CommandException)(ar.exception)).getCommandError();
886                if (err == CommandException.Error.PASSWORD_INCORRECT) {
887                    if (isPinCommand()) {
888                        // look specifically for the PUK commands and adjust
889                        // the message accordingly.
890                        if (sc.equals(SC_PUK) || sc.equals(SC_PUK2)) {
891                            sb.append(context.getText(
892                                    com.android.internal.R.string.badPuk));
893                        } else {
894                            sb.append(context.getText(
895                                    com.android.internal.R.string.badPin));
896                        }
897                    } else {
898                        sb.append(context.getText(
899                                com.android.internal.R.string.passwordIncorrect));
900                    }
901                } else if (err == CommandException.Error.SIM_PUK2) {
902                    sb.append(context.getText(
903                            com.android.internal.R.string.badPin));
904                    sb.append("\n");
905                    sb.append(context.getText(
906                            com.android.internal.R.string.needPuk2));
907                } else {
908                    sb.append(context.getText(
909                            com.android.internal.R.string.mmiError));
910                }
911            } else {
912                sb.append(context.getText(
913                        com.android.internal.R.string.mmiError));
914            }
915        } else if (isActivate()) {
916            state = State.COMPLETE;
917            sb.append(context.getText(
918                    com.android.internal.R.string.serviceEnabled));
919            // Record CLIR setting
920            if (sc.equals(SC_CLIR)) {
921                phone.saveClirSetting(CommandsInterface.CLIR_INVOCATION);
922            }
923        } else if (isDeactivate()) {
924            state = State.COMPLETE;
925            sb.append(context.getText(
926                    com.android.internal.R.string.serviceDisabled));
927            // Record CLIR setting
928            if (sc.equals(SC_CLIR)) {
929                phone.saveClirSetting(CommandsInterface.CLIR_SUPPRESSION);
930            }
931        } else if (isRegister()) {
932            state = State.COMPLETE;
933            sb.append(context.getText(
934                    com.android.internal.R.string.serviceRegistered));
935        } else if (isErasure()) {
936            state = State.COMPLETE;
937            sb.append(context.getText(
938                    com.android.internal.R.string.serviceErased));
939        } else {
940            state = State.FAILED;
941            sb.append(context.getText(
942                    com.android.internal.R.string.mmiError));
943        }
944
945        message = sb;
946        phone.onMMIDone(this);
947    }
948
949    private void
950    onGetClirComplete(AsyncResult ar) {
951        StringBuilder sb = new StringBuilder(getScString());
952        sb.append("\n");
953
954        if (ar.exception != null) {
955            state = State.FAILED;
956            sb.append(context.getText(com.android.internal.R.string.mmiError));
957        } else {
958            int clirArgs[];
959
960            clirArgs = (int[])ar.result;
961
962            // the 'm' parameter from TS 27.007 7.7
963            switch (clirArgs[1]) {
964                case 0: // CLIR not provisioned
965                    sb.append(context.getText(
966                                com.android.internal.R.string.serviceNotProvisioned));
967                    state = State.COMPLETE;
968                break;
969
970                case 1: // CLIR provisioned in permanent mode
971                    sb.append(context.getText(
972                                com.android.internal.R.string.CLIRPermanent));
973                    state = State.COMPLETE;
974                break;
975
976                case 2: // unknown (e.g. no network, etc.)
977                    sb.append(context.getText(
978                                com.android.internal.R.string.mmiError));
979                    state = State.FAILED;
980                break;
981
982                case 3: // CLIR temporary mode presentation restricted
983
984                    // the 'n' parameter from TS 27.007 7.7
985                    switch (clirArgs[0]) {
986                        default:
987                        case 0: // Default
988                            sb.append(context.getText(
989                                    com.android.internal.R.string.CLIRDefaultOnNextCallOn));
990                        break;
991                        case 1: // CLIR invocation
992                            sb.append(context.getText(
993                                    com.android.internal.R.string.CLIRDefaultOnNextCallOn));
994                        break;
995                        case 2: // CLIR suppression
996                            sb.append(context.getText(
997                                    com.android.internal.R.string.CLIRDefaultOnNextCallOff));
998                        break;
999                    }
1000                    state = State.COMPLETE;
1001                break;
1002
1003                case 4: // CLIR temporary mode presentation allowed
1004                    // the 'n' parameter from TS 27.007 7.7
1005                    switch (clirArgs[0]) {
1006                        default:
1007                        case 0: // Default
1008                            sb.append(context.getText(
1009                                    com.android.internal.R.string.CLIRDefaultOffNextCallOff));
1010                        break;
1011                        case 1: // CLIR invocation
1012                            sb.append(context.getText(
1013                                    com.android.internal.R.string.CLIRDefaultOffNextCallOn));
1014                        break;
1015                        case 2: // CLIR suppression
1016                            sb.append(context.getText(
1017                                    com.android.internal.R.string.CLIRDefaultOffNextCallOff));
1018                        break;
1019                    }
1020
1021                    state = State.COMPLETE;
1022                break;
1023            }
1024        }
1025
1026        message = sb;
1027        phone.onMMIDone(this);
1028    }
1029
1030    /**
1031     * @param serviceClass 1 bit of the service class bit vectory
1032     * @return String to be used for call forward query MMI response text.
1033     *        Returns null if unrecognized
1034     */
1035
1036    private CharSequence
1037    serviceClassToCFString (int serviceClass) {
1038        switch (serviceClass) {
1039            case SERVICE_CLASS_VOICE:
1040                return context.getText(com.android.internal.R.string.serviceClassVoice);
1041            case SERVICE_CLASS_DATA:
1042                return context.getText(com.android.internal.R.string.serviceClassData);
1043            case SERVICE_CLASS_FAX:
1044                return context.getText(com.android.internal.R.string.serviceClassFAX);
1045            case SERVICE_CLASS_SMS:
1046                return context.getText(com.android.internal.R.string.serviceClassSMS);
1047            case SERVICE_CLASS_DATA_SYNC:
1048                return context.getText(com.android.internal.R.string.serviceClassDataSync);
1049            case SERVICE_CLASS_DATA_ASYNC:
1050                return context.getText(com.android.internal.R.string.serviceClassDataAsync);
1051            case SERVICE_CLASS_PACKET:
1052                return context.getText(com.android.internal.R.string.serviceClassPacket);
1053            case SERVICE_CLASS_PAD:
1054                return context.getText(com.android.internal.R.string.serviceClassPAD);
1055            default:
1056                return null;
1057        }
1058    }
1059
1060
1061    /** one CallForwardInfo + serviceClassMask -> one line of text */
1062    private CharSequence
1063    makeCFQueryResultMessage(CallForwardInfo info, int serviceClassMask) {
1064        CharSequence template;
1065        String sources[] = {"{0}", "{1}", "{2}"};
1066        CharSequence destinations[] = new CharSequence[3];
1067        boolean needTimeTemplate;
1068
1069        // CF_REASON_NO_REPLY also has a time value associated with
1070        // it. All others don't.
1071
1072        needTimeTemplate =
1073            (info.reason == CommandsInterface.CF_REASON_NO_REPLY);
1074
1075        if (info.status == 1) {
1076            if (needTimeTemplate) {
1077                template = context.getText(
1078                        com.android.internal.R.string.cfTemplateForwardedTime);
1079            } else {
1080                template = context.getText(
1081                        com.android.internal.R.string.cfTemplateForwarded);
1082            }
1083        } else if (info.status == 0 && isEmptyOrNull(info.number)) {
1084            template = context.getText(
1085                        com.android.internal.R.string.cfTemplateNotForwarded);
1086        } else { /* (info.status == 0) && !isEmptyOrNull(info.number) */
1087            // A call forward record that is not active but contains
1088            // a phone number is considered "registered"
1089
1090            if (needTimeTemplate) {
1091                template = context.getText(
1092                        com.android.internal.R.string.cfTemplateRegisteredTime);
1093            } else {
1094                template = context.getText(
1095                        com.android.internal.R.string.cfTemplateRegistered);
1096            }
1097        }
1098
1099        // In the template (from strings.xmls)
1100        //         {0} is one of "bearerServiceCode*"
1101        //        {1} is dialing number
1102        //      {2} is time in seconds
1103
1104        destinations[0] = serviceClassToCFString(info.serviceClass & serviceClassMask);
1105        destinations[1] = PhoneNumberUtils.stringFromStringAndTOA(info.number, info.toa);
1106        destinations[2] = Integer.toString(info.timeSeconds);
1107
1108        if (info.reason == CommandsInterface.CF_REASON_UNCONDITIONAL &&
1109                (info.serviceClass & serviceClassMask)
1110                        == CommandsInterface.SERVICE_CLASS_VOICE) {
1111            boolean cffEnabled = (info.status == 1);
1112            phone.mSIMRecords.setVoiceCallForwardingFlag(1, cffEnabled);
1113        }
1114
1115        return TextUtils.replace(template, sources, destinations);
1116    }
1117
1118
1119    private void
1120    onQueryCfComplete(AsyncResult ar) {
1121        StringBuilder sb = new StringBuilder(getScString());
1122        sb.append("\n");
1123
1124        if (ar.exception != null) {
1125            state = State.FAILED;
1126            sb.append(context.getText(com.android.internal.R.string.mmiError));
1127        } else {
1128            CallForwardInfo infos[];
1129
1130            infos = (CallForwardInfo[]) ar.result;
1131
1132            if (infos.length == 0) {
1133                // Assume the default is not active
1134                sb.append(context.getText(com.android.internal.R.string.serviceDisabled));
1135
1136                // Set unconditional CFF in SIM to false
1137                phone.mSIMRecords.setVoiceCallForwardingFlag(1, false);
1138            } else {
1139
1140                SpannableStringBuilder tb = new SpannableStringBuilder();
1141
1142                // Each bit in the service class gets its own result line
1143                // The service classes may be split up over multiple
1144                // CallForwardInfos. So, for each service classs, find out
1145                // which CallForwardInfo represents it and then build
1146                // the response text based on that
1147
1148                for (int serviceClassMask = 1
1149                            ; serviceClassMask <= SERVICE_CLASS_MAX
1150                            ; serviceClassMask <<= 1
1151                ) {
1152                    for (int i = 0, s = infos.length; i < s ; i++) {
1153                        if ((serviceClassMask & infos[i].serviceClass) != 0) {
1154                            tb.append(makeCFQueryResultMessage(infos[i],
1155                                            serviceClassMask));
1156                            tb.append("\n");
1157                        }
1158                    }
1159                }
1160                sb.append(tb);
1161            }
1162
1163            state = State.COMPLETE;
1164        }
1165
1166        message = sb;
1167        phone.onMMIDone(this);
1168
1169    }
1170
1171    private void
1172    onQueryComplete(AsyncResult ar) {
1173        StringBuilder sb = new StringBuilder(getScString());
1174        sb.append("\n");
1175
1176        if (ar.exception != null) {
1177            state = State.FAILED;
1178            sb.append(context.getText(com.android.internal.R.string.mmiError));
1179        } else {
1180            int[] ints = (int[])ar.result;
1181
1182            if (ints.length != 0) {
1183                if (ints[0] == 0) {
1184                    sb.append(context.getText(com.android.internal.R.string.serviceDisabled));
1185                } else if (sc.equals(SC_WAIT)) {
1186                    // Call Waiting includes additional data in the response.
1187                    sb.append(createQueryCallWaitingResultMessage(ints[1]));
1188                } else if (isServiceCodeCallBarring(sc)) {
1189                    // ints[0] for Call Barring is a bit vector of services
1190                    sb.append(createQueryCallBarringResultMessage(ints[0]));
1191                } else if (ints[0] == 1) {
1192                    // for all other services, treat it as a boolean
1193                    sb.append(context.getText(com.android.internal.R.string.serviceEnabled));
1194                } else {
1195                    sb.append(context.getText(com.android.internal.R.string.mmiError));
1196                }
1197            } else {
1198                sb.append(context.getText(com.android.internal.R.string.mmiError));
1199            }
1200            state = State.COMPLETE;
1201        }
1202
1203        message = sb;
1204        phone.onMMIDone(this);
1205    }
1206
1207    private CharSequence
1208    createQueryCallWaitingResultMessage(int serviceClass) {
1209        StringBuilder sb =
1210                new StringBuilder(context.getText(com.android.internal.R.string.serviceEnabledFor));
1211
1212        for (int classMask = 1
1213                    ; classMask <= SERVICE_CLASS_MAX
1214                    ; classMask <<= 1
1215        ) {
1216            if ((classMask & serviceClass) != 0) {
1217                sb.append("\n");
1218                sb.append(serviceClassToCFString(classMask & serviceClass));
1219            }
1220        }
1221        return sb;
1222    }
1223    private CharSequence
1224    createQueryCallBarringResultMessage(int serviceClass)
1225    {
1226        StringBuilder sb = new StringBuilder(context.getText(com.android.internal.R.string.serviceEnabledFor));
1227
1228        for (int classMask = 1
1229                    ; classMask <= SERVICE_CLASS_MAX
1230                    ; classMask <<= 1
1231        ) {
1232            if ((classMask & serviceClass) != 0) {
1233                sb.append("\n");
1234                sb.append(serviceClassToCFString(classMask & serviceClass));
1235            }
1236        }
1237        return sb;
1238    }
1239
1240    /***
1241     * TODO: It would be nice to have a method here that can take in a dialstring and
1242     * figure out if there is an MMI code embedded within it.  This code would replace
1243     * some of the string parsing functionality in the Phone App's
1244     * SpecialCharSequenceMgr class.
1245     */
1246
1247}
1248