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                                (((serviceClass & CommandsInterface.SERVICE_CLASS_VOICE) != 0) ||
600                                 (serviceClass == CommandsInterface.SERVICE_CLASS_NONE))) ? 1 : 0;
601
602                    int isEnableDesired =
603                        ((cfAction == CommandsInterface.CF_ACTION_ENABLE) ||
604                                (cfAction == CommandsInterface.CF_ACTION_REGISTRATION)) ? 1 : 0;
605
606                    Log.d(LOG_TAG, "is CF setCallForward");
607                    phone.mCM.setCallForward(cfAction, reason, serviceClass,
608                            dialingNumber, time, obtainMessage(
609                                    EVENT_SET_CFF_COMPLETE,
610                                    isSettingUnconditionalVoice,
611                                    isEnableDesired, this));
612                }
613            } else if (isServiceCodeCallBarring(sc)) {
614                // sia = password
615                // sib = basic service group
616
617                String password = sia;
618                int serviceClass = siToServiceClass(sib);
619                String facility = scToBarringFacility(sc);
620
621                if (isInterrogate()) {
622                    phone.mCM.queryFacilityLock(facility, password,
623                            serviceClass, obtainMessage(EVENT_QUERY_COMPLETE, this));
624                } else if (isActivate() || isDeactivate()) {
625                    phone.mCM.setFacilityLock(facility, isActivate(), password,
626                            serviceClass, obtainMessage(EVENT_SET_COMPLETE, this));
627                } else {
628                    throw new RuntimeException ("Invalid or Unsupported MMI Code");
629                }
630
631            } else if (sc != null && sc.equals(SC_PWD)) {
632                // sia = fac
633                // sib = old pwd
634                // sic = new pwd
635                // pwd = new pwd
636                String facility;
637                String oldPwd = sib;
638                String newPwd = sic;
639                if (isActivate() || isRegister()) {
640                    // Even though ACTIVATE is acceptable, this is really termed a REGISTER
641                    action = ACTION_REGISTER;
642
643                    if (sia == null) {
644                        // If sc was not specified, treat it as BA_ALL.
645                        facility = CommandsInterface.CB_FACILITY_BA_ALL;
646                    } else {
647                        facility = scToBarringFacility(sia);
648                    }
649                    if (newPwd.equals(pwd)) {
650                        phone.mCM.changeBarringPassword(facility, oldPwd,
651                                newPwd, obtainMessage(EVENT_SET_COMPLETE, this));
652                    } else {
653                        // password mismatch; return error
654                        handlePasswordError(com.android.internal.R.string.passwordIncorrect);
655                    }
656                } else {
657                    throw new RuntimeException ("Invalid or Unsupported MMI Code");
658                }
659
660            } else if (sc != null && sc.equals(SC_WAIT)) {
661                // sia = basic service group
662                int serviceClass = siToServiceClass(sia);
663
664                if (isActivate() || isDeactivate()) {
665                    phone.mCM.setCallWaiting(isActivate(), serviceClass,
666                            obtainMessage(EVENT_SET_COMPLETE, this));
667                } else if (isInterrogate()) {
668                    phone.mCM.queryCallWaiting(serviceClass,
669                            obtainMessage(EVENT_QUERY_COMPLETE, this));
670                } else {
671                    throw new RuntimeException ("Invalid or Unsupported MMI Code");
672                }
673            } else if (isPinCommand()) {
674                // sia = old PIN or PUK
675                // sib = new PIN
676                // sic = new PIN
677                String oldPinOrPuk = sia;
678                String newPin = sib;
679                int pinLen = newPin.length();
680                if (isRegister()) {
681                    if (!newPin.equals(sic)) {
682                        // password mismatch; return error
683                        handlePasswordError(com.android.internal.R.string.mismatchPin);
684                    } else if (pinLen < 4 || pinLen > 8 ) {
685                        // invalid length
686                        handlePasswordError(com.android.internal.R.string.invalidPin);
687                    } else if (sc.equals(SC_PIN) &&
688                               phone.mSimCard.getState() == SimCard.State.PUK_REQUIRED ) {
689                        // Sim is puk-locked
690                        handlePasswordError(com.android.internal.R.string.needPuk);
691                    } else {
692                        // pre-checks OK
693                        if (sc.equals(SC_PIN)) {
694                            phone.mCM.changeIccPin(oldPinOrPuk, newPin,
695                                    obtainMessage(EVENT_SET_COMPLETE, this));
696                        } else if (sc.equals(SC_PIN2)) {
697                            phone.mCM.changeIccPin2(oldPinOrPuk, newPin,
698                                    obtainMessage(EVENT_SET_COMPLETE, this));
699                        } else if (sc.equals(SC_PUK)) {
700                            phone.mCM.supplyIccPuk(oldPinOrPuk, newPin,
701                                    obtainMessage(EVENT_SET_COMPLETE, this));
702                        } else if (sc.equals(SC_PUK2)) {
703                            phone.mCM.supplyIccPuk2(oldPinOrPuk, newPin,
704                                    obtainMessage(EVENT_SET_COMPLETE, this));
705                        }
706                    }
707                } else {
708                    throw new RuntimeException ("Invalid or Unsupported MMI Code");
709                }
710            } else if (poundString != null) {
711                sendUssd(poundString);
712            } else {
713                throw new RuntimeException ("Invalid or Unsupported MMI Code");
714            }
715        } catch (RuntimeException exc) {
716            state = State.FAILED;
717            message = context.getText(com.android.internal.R.string.mmiError);
718            phone.onMMIDone(this);
719        }
720    }
721
722    private void handlePasswordError(int res) {
723        state = State.FAILED;
724        StringBuilder sb = new StringBuilder(getScString());
725        sb.append("\n");
726        sb.append(context.getText(res));
727        message = sb;
728        phone.onMMIDone(this);
729    }
730
731    /**
732     * Called from GSMPhone
733     *
734     * An unsolicited USSD NOTIFY or REQUEST has come in matching
735     * up with this pending USSD request
736     *
737     * Note: If REQUEST, this exchange is complete, but the session remains
738     *       active (ie, the network expects user input).
739     */
740    void
741    onUssdFinished(String ussdMessage, boolean isUssdRequest) {
742        if (state == State.PENDING) {
743            if (ussdMessage == null) {
744                message = context.getText(com.android.internal.R.string.mmiComplete);
745            } else {
746                message = ussdMessage;
747            }
748            this.isUssdRequest = isUssdRequest;
749            // If it's a request, leave it PENDING so that it's cancelable.
750            if (!isUssdRequest) {
751                state = State.COMPLETE;
752            }
753
754            phone.onMMIDone(this);
755        }
756    }
757
758    /**
759     * Called from GSMPhone
760     *
761     * The radio has reset, and this is still pending
762     */
763
764    void
765    onUssdFinishedError() {
766        if (state == State.PENDING) {
767            state = State.FAILED;
768            message = context.getText(com.android.internal.R.string.mmiError);
769
770            phone.onMMIDone(this);
771        }
772    }
773
774    void sendUssd(String ussdMessage) {
775        // Treat this as a USSD string
776        isPendingUSSD = true;
777
778        // Note that unlike most everything else, the USSD complete
779        // response does not complete this MMI code...we wait for
780        // an unsolicited USSD "Notify" or "Request".
781        // The matching up of this is doene in GSMPhone.
782
783        phone.mCM.sendUSSD(ussdMessage,
784            obtainMessage(EVENT_USSD_COMPLETE, this));
785    }
786
787    /** Called from GSMPhone.handleMessage; not a Handler subclass */
788    public void
789    handleMessage (Message msg) {
790        AsyncResult ar;
791
792        switch (msg.what) {
793            case EVENT_SET_COMPLETE:
794                ar = (AsyncResult) (msg.obj);
795
796                onSetComplete(ar);
797                break;
798
799            case EVENT_SET_CFF_COMPLETE:
800                ar = (AsyncResult) (msg.obj);
801
802                /*
803                * msg.arg1 = 1 means to set unconditional voice call forwarding
804                * msg.arg2 = 1 means to enable voice call forwarding
805                */
806                if ((ar.exception == null) && (msg.arg1 == 1)) {
807                    boolean cffEnabled = (msg.arg2 == 1);
808                    phone.mSIMRecords.setVoiceCallForwardingFlag(1, cffEnabled);
809                }
810
811                onSetComplete(ar);
812                break;
813
814            case EVENT_GET_CLIR_COMPLETE:
815                ar = (AsyncResult) (msg.obj);
816                onGetClirComplete(ar);
817            break;
818
819            case EVENT_QUERY_CF_COMPLETE:
820                ar = (AsyncResult) (msg.obj);
821                onQueryCfComplete(ar);
822            break;
823
824            case EVENT_QUERY_COMPLETE:
825                ar = (AsyncResult) (msg.obj);
826                onQueryComplete(ar);
827            break;
828
829            case EVENT_USSD_COMPLETE:
830                ar = (AsyncResult) (msg.obj);
831
832                if (ar.exception != null) {
833                    state = State.FAILED;
834                    message = context.getText(
835                                            com.android.internal.R.string.mmiError);
836
837                    phone.onMMIDone(this);
838                }
839
840                // Note that unlike most everything else, the USSD complete
841                // response does not complete this MMI code...we wait for
842                // an unsolicited USSD "Notify" or "Request".
843                // The matching up of this is done in GSMPhone.
844
845            break;
846
847            case EVENT_USSD_CANCEL_COMPLETE:
848                phone.onMMIDone(this);
849            break;
850        }
851    }
852    //***** Private instance methods
853
854    private CharSequence getScString() {
855        if (sc != null) {
856            if (isServiceCodeCallBarring(sc)) {
857                return context.getText(com.android.internal.R.string.BaMmi);
858            } else if (isServiceCodeCallForwarding(sc)) {
859                return context.getText(com.android.internal.R.string.CfMmi);
860            } else if (sc.equals(SC_CLIP)) {
861                return context.getText(com.android.internal.R.string.ClipMmi);
862            } else if (sc.equals(SC_CLIR)) {
863                return context.getText(com.android.internal.R.string.ClirMmi);
864            } else if (sc.equals(SC_PWD)) {
865                return context.getText(com.android.internal.R.string.PwdMmi);
866            } else if (sc.equals(SC_WAIT)) {
867                return context.getText(com.android.internal.R.string.CwMmi);
868            } else if (isPinCommand()) {
869                return context.getText(com.android.internal.R.string.PinMmi);
870            }
871        }
872
873        return "";
874    }
875
876    private void
877    onSetComplete(AsyncResult ar){
878        StringBuilder sb = new StringBuilder(getScString());
879        sb.append("\n");
880
881        if (ar.exception != null) {
882            state = State.FAILED;
883            if (ar.exception instanceof CommandException) {
884                CommandException.Error err = ((CommandException)(ar.exception)).getCommandError();
885                if (err == CommandException.Error.PASSWORD_INCORRECT) {
886                    if (isPinCommand()) {
887                        // look specifically for the PUK commands and adjust
888                        // the message accordingly.
889                        if (sc.equals(SC_PUK) || sc.equals(SC_PUK2)) {
890                            sb.append(context.getText(
891                                    com.android.internal.R.string.badPuk));
892                        } else {
893                            sb.append(context.getText(
894                                    com.android.internal.R.string.badPin));
895                        }
896                    } else {
897                        sb.append(context.getText(
898                                com.android.internal.R.string.passwordIncorrect));
899                    }
900                } else if (err == CommandException.Error.SIM_PUK2) {
901                    sb.append(context.getText(
902                            com.android.internal.R.string.badPin));
903                    sb.append("\n");
904                    sb.append(context.getText(
905                            com.android.internal.R.string.needPuk2));
906                } else {
907                    sb.append(context.getText(
908                            com.android.internal.R.string.mmiError));
909                }
910            } else {
911                sb.append(context.getText(
912                        com.android.internal.R.string.mmiError));
913            }
914        } else if (isActivate()) {
915            state = State.COMPLETE;
916            sb.append(context.getText(
917                    com.android.internal.R.string.serviceEnabled));
918            // Record CLIR setting
919            if (sc.equals(SC_CLIR)) {
920                phone.saveClirSetting(CommandsInterface.CLIR_INVOCATION);
921            }
922        } else if (isDeactivate()) {
923            state = State.COMPLETE;
924            sb.append(context.getText(
925                    com.android.internal.R.string.serviceDisabled));
926            // Record CLIR setting
927            if (sc.equals(SC_CLIR)) {
928                phone.saveClirSetting(CommandsInterface.CLIR_SUPPRESSION);
929            }
930        } else if (isRegister()) {
931            state = State.COMPLETE;
932            sb.append(context.getText(
933                    com.android.internal.R.string.serviceRegistered));
934        } else if (isErasure()) {
935            state = State.COMPLETE;
936            sb.append(context.getText(
937                    com.android.internal.R.string.serviceErased));
938        } else {
939            state = State.FAILED;
940            sb.append(context.getText(
941                    com.android.internal.R.string.mmiError));
942        }
943
944        message = sb;
945        phone.onMMIDone(this);
946    }
947
948    private void
949    onGetClirComplete(AsyncResult ar) {
950        StringBuilder sb = new StringBuilder(getScString());
951        sb.append("\n");
952
953        if (ar.exception != null) {
954            state = State.FAILED;
955            sb.append(context.getText(com.android.internal.R.string.mmiError));
956        } else {
957            int clirArgs[];
958
959            clirArgs = (int[])ar.result;
960
961            // the 'm' parameter from TS 27.007 7.7
962            switch (clirArgs[1]) {
963                case 0: // CLIR not provisioned
964                    sb.append(context.getText(
965                                com.android.internal.R.string.serviceNotProvisioned));
966                    state = State.COMPLETE;
967                break;
968
969                case 1: // CLIR provisioned in permanent mode
970                    sb.append(context.getText(
971                                com.android.internal.R.string.CLIRPermanent));
972                    state = State.COMPLETE;
973                break;
974
975                case 2: // unknown (e.g. no network, etc.)
976                    sb.append(context.getText(
977                                com.android.internal.R.string.mmiError));
978                    state = State.FAILED;
979                break;
980
981                case 3: // CLIR temporary mode presentation restricted
982
983                    // the 'n' parameter from TS 27.007 7.7
984                    switch (clirArgs[0]) {
985                        default:
986                        case 0: // Default
987                            sb.append(context.getText(
988                                    com.android.internal.R.string.CLIRDefaultOnNextCallOn));
989                        break;
990                        case 1: // CLIR invocation
991                            sb.append(context.getText(
992                                    com.android.internal.R.string.CLIRDefaultOnNextCallOn));
993                        break;
994                        case 2: // CLIR suppression
995                            sb.append(context.getText(
996                                    com.android.internal.R.string.CLIRDefaultOnNextCallOff));
997                        break;
998                    }
999                    state = State.COMPLETE;
1000                break;
1001
1002                case 4: // CLIR temporary mode presentation allowed
1003                    // the 'n' parameter from TS 27.007 7.7
1004                    switch (clirArgs[0]) {
1005                        default:
1006                        case 0: // Default
1007                            sb.append(context.getText(
1008                                    com.android.internal.R.string.CLIRDefaultOffNextCallOff));
1009                        break;
1010                        case 1: // CLIR invocation
1011                            sb.append(context.getText(
1012                                    com.android.internal.R.string.CLIRDefaultOffNextCallOn));
1013                        break;
1014                        case 2: // CLIR suppression
1015                            sb.append(context.getText(
1016                                    com.android.internal.R.string.CLIRDefaultOffNextCallOff));
1017                        break;
1018                    }
1019
1020                    state = State.COMPLETE;
1021                break;
1022            }
1023        }
1024
1025        message = sb;
1026        phone.onMMIDone(this);
1027    }
1028
1029    /**
1030     * @param serviceClass 1 bit of the service class bit vectory
1031     * @return String to be used for call forward query MMI response text.
1032     *        Returns null if unrecognized
1033     */
1034
1035    private CharSequence
1036    serviceClassToCFString (int serviceClass) {
1037        switch (serviceClass) {
1038            case SERVICE_CLASS_VOICE:
1039                return context.getText(com.android.internal.R.string.serviceClassVoice);
1040            case SERVICE_CLASS_DATA:
1041                return context.getText(com.android.internal.R.string.serviceClassData);
1042            case SERVICE_CLASS_FAX:
1043                return context.getText(com.android.internal.R.string.serviceClassFAX);
1044            case SERVICE_CLASS_SMS:
1045                return context.getText(com.android.internal.R.string.serviceClassSMS);
1046            case SERVICE_CLASS_DATA_SYNC:
1047                return context.getText(com.android.internal.R.string.serviceClassDataSync);
1048            case SERVICE_CLASS_DATA_ASYNC:
1049                return context.getText(com.android.internal.R.string.serviceClassDataAsync);
1050            case SERVICE_CLASS_PACKET:
1051                return context.getText(com.android.internal.R.string.serviceClassPacket);
1052            case SERVICE_CLASS_PAD:
1053                return context.getText(com.android.internal.R.string.serviceClassPAD);
1054            default:
1055                return null;
1056        }
1057    }
1058
1059
1060    /** one CallForwardInfo + serviceClassMask -> one line of text */
1061    private CharSequence
1062    makeCFQueryResultMessage(CallForwardInfo info, int serviceClassMask) {
1063        CharSequence template;
1064        String sources[] = {"{0}", "{1}", "{2}"};
1065        CharSequence destinations[] = new CharSequence[3];
1066        boolean needTimeTemplate;
1067
1068        // CF_REASON_NO_REPLY also has a time value associated with
1069        // it. All others don't.
1070
1071        needTimeTemplate =
1072            (info.reason == CommandsInterface.CF_REASON_NO_REPLY);
1073
1074        if (info.status == 1) {
1075            if (needTimeTemplate) {
1076                template = context.getText(
1077                        com.android.internal.R.string.cfTemplateForwardedTime);
1078            } else {
1079                template = context.getText(
1080                        com.android.internal.R.string.cfTemplateForwarded);
1081            }
1082        } else if (info.status == 0 && isEmptyOrNull(info.number)) {
1083            template = context.getText(
1084                        com.android.internal.R.string.cfTemplateNotForwarded);
1085        } else { /* (info.status == 0) && !isEmptyOrNull(info.number) */
1086            // A call forward record that is not active but contains
1087            // a phone number is considered "registered"
1088
1089            if (needTimeTemplate) {
1090                template = context.getText(
1091                        com.android.internal.R.string.cfTemplateRegisteredTime);
1092            } else {
1093                template = context.getText(
1094                        com.android.internal.R.string.cfTemplateRegistered);
1095            }
1096        }
1097
1098        // In the template (from strings.xmls)
1099        //         {0} is one of "bearerServiceCode*"
1100        //        {1} is dialing number
1101        //      {2} is time in seconds
1102
1103        destinations[0] = serviceClassToCFString(info.serviceClass & serviceClassMask);
1104        destinations[1] = PhoneNumberUtils.stringFromStringAndTOA(info.number, info.toa);
1105        destinations[2] = Integer.toString(info.timeSeconds);
1106
1107        if (info.reason == CommandsInterface.CF_REASON_UNCONDITIONAL &&
1108                (info.serviceClass & serviceClassMask)
1109                        == CommandsInterface.SERVICE_CLASS_VOICE) {
1110            boolean cffEnabled = (info.status == 1);
1111            phone.mSIMRecords.setVoiceCallForwardingFlag(1, cffEnabled);
1112        }
1113
1114        return TextUtils.replace(template, sources, destinations);
1115    }
1116
1117
1118    private void
1119    onQueryCfComplete(AsyncResult ar) {
1120        StringBuilder sb = new StringBuilder(getScString());
1121        sb.append("\n");
1122
1123        if (ar.exception != null) {
1124            state = State.FAILED;
1125            sb.append(context.getText(com.android.internal.R.string.mmiError));
1126        } else {
1127            CallForwardInfo infos[];
1128
1129            infos = (CallForwardInfo[]) ar.result;
1130
1131            if (infos.length == 0) {
1132                // Assume the default is not active
1133                sb.append(context.getText(com.android.internal.R.string.serviceDisabled));
1134
1135                // Set unconditional CFF in SIM to false
1136                phone.mSIMRecords.setVoiceCallForwardingFlag(1, false);
1137            } else {
1138
1139                SpannableStringBuilder tb = new SpannableStringBuilder();
1140
1141                // Each bit in the service class gets its own result line
1142                // The service classes may be split up over multiple
1143                // CallForwardInfos. So, for each service classs, find out
1144                // which CallForwardInfo represents it and then build
1145                // the response text based on that
1146
1147                for (int serviceClassMask = 1
1148                            ; serviceClassMask <= SERVICE_CLASS_MAX
1149                            ; serviceClassMask <<= 1
1150                ) {
1151                    for (int i = 0, s = infos.length; i < s ; i++) {
1152                        if ((serviceClassMask & infos[i].serviceClass) != 0) {
1153                            tb.append(makeCFQueryResultMessage(infos[i],
1154                                            serviceClassMask));
1155                            tb.append("\n");
1156                        }
1157                    }
1158                }
1159                sb.append(tb);
1160            }
1161
1162            state = State.COMPLETE;
1163        }
1164
1165        message = sb;
1166        phone.onMMIDone(this);
1167
1168    }
1169
1170    private void
1171    onQueryComplete(AsyncResult ar) {
1172        StringBuilder sb = new StringBuilder(getScString());
1173        sb.append("\n");
1174
1175        if (ar.exception != null) {
1176            state = State.FAILED;
1177            sb.append(context.getText(com.android.internal.R.string.mmiError));
1178        } else {
1179            int[] ints = (int[])ar.result;
1180
1181            if (ints.length != 0) {
1182                if (ints[0] == 0) {
1183                    sb.append(context.getText(com.android.internal.R.string.serviceDisabled));
1184                } else if (sc.equals(SC_WAIT)) {
1185                    // Call Waiting includes additional data in the response.
1186                    sb.append(createQueryCallWaitingResultMessage(ints[1]));
1187                } else if (isServiceCodeCallBarring(sc)) {
1188                    // ints[0] for Call Barring is a bit vector of services
1189                    sb.append(createQueryCallBarringResultMessage(ints[0]));
1190                } else if (ints[0] == 1) {
1191                    // for all other services, treat it as a boolean
1192                    sb.append(context.getText(com.android.internal.R.string.serviceEnabled));
1193                } else {
1194                    sb.append(context.getText(com.android.internal.R.string.mmiError));
1195                }
1196            } else {
1197                sb.append(context.getText(com.android.internal.R.string.mmiError));
1198            }
1199            state = State.COMPLETE;
1200        }
1201
1202        message = sb;
1203        phone.onMMIDone(this);
1204    }
1205
1206    private CharSequence
1207    createQueryCallWaitingResultMessage(int serviceClass) {
1208        StringBuilder sb =
1209                new StringBuilder(context.getText(com.android.internal.R.string.serviceEnabledFor));
1210
1211        for (int classMask = 1
1212                    ; classMask <= SERVICE_CLASS_MAX
1213                    ; classMask <<= 1
1214        ) {
1215            if ((classMask & serviceClass) != 0) {
1216                sb.append("\n");
1217                sb.append(serviceClassToCFString(classMask & serviceClass));
1218            }
1219        }
1220        return sb;
1221    }
1222    private CharSequence
1223    createQueryCallBarringResultMessage(int serviceClass)
1224    {
1225        StringBuilder sb = new StringBuilder(context.getText(com.android.internal.R.string.serviceEnabledFor));
1226
1227        for (int classMask = 1
1228                    ; classMask <= SERVICE_CLASS_MAX
1229                    ; classMask <<= 1
1230        ) {
1231            if ((classMask & serviceClass) != 0) {
1232                sb.append("\n");
1233                sb.append(serviceClassToCFString(classMask & serviceClass));
1234            }
1235        }
1236        return sb;
1237    }
1238
1239    /***
1240     * TODO: It would be nice to have a method here that can take in a dialstring and
1241     * figure out if there is an MMI code embedded within it.  This code would replace
1242     * some of the string parsing functionality in the Phone App's
1243     * SpecialCharSequenceMgr class.
1244     */
1245
1246}
1247