2 * Copyright (C) 2006 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.internal.telephony.gsm;
19 import android.content.Context;
20 import com.android.internal.telephony.*;
23 import android.telephony.PhoneNumberUtils;
24 import android.text.SpannableStringBuilder;
25 import android.text.TextUtils;
26 import android.util.Log;
28 import static com.android.internal.telephony.CommandsInterface.*;
30 import java.util.regex.Pattern;
31 import java.util.regex.Matcher;
34 * The motto for this file is:
36 * "NOTE: By using the # as a separator, most cases are expected to be unambiguous."
42 public final class GsmMmiCode extends Handler implements MmiCode {
43 static final String LOG_TAG = "GSM";
47 // Max Size of the Short Code (aka Short String from TS 22.030 6.5.2)
48 static final int MAX_LENGTH_SHORT_CODE = 2;
50 // TS 22.030 6.5.2 Every Short String USSD command will end with #-key
51 // (known as #-String)
52 static final char END_OF_USSD_COMMAND = '#';
54 // From TS 22.030 6.5.2
55 static final String ACTION_ACTIVATE = "*";
56 static final String ACTION_DEACTIVATE = "#";
57 static final String ACTION_INTERROGATE = "*#";
58 static final String ACTION_REGISTER = "**";
59 static final String ACTION_ERASURE = "##";
61 // Supp Service codes from TS 22.030 Annex B
63 //Called line presentation
64 static final String SC_CLIP = "30";
65 static final String SC_CLIR = "31";
68 static final String SC_CFU = "21";
69 static final String SC_CFB = "67";
70 static final String SC_CFNRy = "61";
71 static final String SC_CFNR = "62";
73 static final String SC_CF_All = "002";
74 static final String SC_CF_All_Conditional = "004";
77 static final String SC_WAIT = "43";
80 static final String SC_BAOC = "33";
81 static final String SC_BAOIC = "331";
82 static final String SC_BAOICxH = "332";
83 static final String SC_BAIC = "35";
84 static final String SC_BAICr = "351";
86 static final String SC_BA_ALL = "330";
87 static final String SC_BA_MO = "333";
88 static final String SC_BA_MT = "353";
90 // Supp Service Password registration
91 static final String SC_PWD = "03";
94 static final String SC_PIN = "04";
95 static final String SC_PIN2 = "042";
96 static final String SC_PUK = "05";
97 static final String SC_PUK2 = "052";
99 //***** Event Constants
101 static final int EVENT_SET_COMPLETE = 1;
102 static final int EVENT_GET_CLIR_COMPLETE = 2;
103 static final int EVENT_QUERY_CF_COMPLETE = 3;
104 static final int EVENT_USSD_COMPLETE = 4;
105 static final int EVENT_QUERY_COMPLETE = 5;
106 static final int EVENT_SET_CFF_COMPLETE = 6;
107 static final int EVENT_USSD_CANCEL_COMPLETE = 7;
109 //***** Instance Variables
114 String action; // One of ACTION_*
115 String sc; // Service Code
116 String sia, sib, sic; // Service Info a,b,c
117 String poundString; // Entire MMI string up to and including #
118 String dialingNumber;
119 String pwd; // For password registration
121 /** Set to true in processCode, not at newFromDialString time */
122 private boolean isPendingUSSD;
124 private boolean isUssdRequest;
126 State state = State.PENDING;
127 CharSequence message;
129 //***** Class Variables
132 // See TS 22.030 6.5.2 "Structure of the MMI"
134 static Pattern sPatternSuppService = Pattern.compile(
135 "((\\*|#|\\*#|\\*\\*|##)(\\d{2,3})(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*))?)?)?)?#)([^#]*)");
136 /* 1 2 3 4 5 6 7 8 9 10 11 12
138 1 = Full string up to and including #
139 2 = action (activation/interrogation/registration/erasure)
144 10 = dialing number which must not include #, e.g. *SCn*SI#DN format
147 static final int MATCH_GROUP_POUND_STRING = 1;
149 static final int MATCH_GROUP_ACTION = 2;
150 //(activation/interrogation/registration/erasure)
152 static final int MATCH_GROUP_SERVICE_CODE = 3;
153 static final int MATCH_GROUP_SIA = 5;
154 static final int MATCH_GROUP_SIB = 7;
155 static final int MATCH_GROUP_SIC = 9;
156 static final int MATCH_GROUP_PWD_CONFIRM = 11;
157 static final int MATCH_GROUP_DIALING_NUMBER = 12;
158 static private String[] sTwoDigitNumberPattern;
160 //***** Public Class methods
163 * Some dial strings in GSM are defined to do non-call setup
164 * things, such as modify or query supplementary service settings (eg, call
165 * forwarding). These are generally referred to as "MMI codes".
166 * We look to see if the dial string contains a valid MMI code (potentially
167 * with a dial string at the end as well) and return info here.
169 * If the dial string contains no MMI code, we return an instance with
170 * only "dialingNumber" set
172 * Please see flow chart in TS 22.030 6.5.3.2
176 newFromDialString(String dialString, GSMPhone phone) {
178 GsmMmiCode ret = null;
180 m = sPatternSuppService.matcher(dialString);
182 // Is this formatted like a standard supplementary service code?
184 ret = new GsmMmiCode(phone);
185 ret.poundString = makeEmptyNull(m.group(MATCH_GROUP_POUND_STRING));
186 ret.action = makeEmptyNull(m.group(MATCH_GROUP_ACTION));
187 ret.sc = makeEmptyNull(m.group(MATCH_GROUP_SERVICE_CODE));
188 ret.sia = makeEmptyNull(m.group(MATCH_GROUP_SIA));
189 ret.sib = makeEmptyNull(m.group(MATCH_GROUP_SIB));
190 ret.sic = makeEmptyNull(m.group(MATCH_GROUP_SIC));
191 ret.pwd = makeEmptyNull(m.group(MATCH_GROUP_PWD_CONFIRM));
192 ret.dialingNumber = makeEmptyNull(m.group(MATCH_GROUP_DIALING_NUMBER));
194 } else if (dialString.endsWith("#")) {
195 // TS 22.030 sec 6.5.3.2
196 // "Entry of any characters defined in the 3GPP TS 23.038 [8] Default Alphabet
197 // (up to the maximum defined in 3GPP TS 24.080 [10]), followed by #SEND".
199 ret = new GsmMmiCode(phone);
200 ret.poundString = dialString;
201 } else if (isTwoDigitShortCode(phone.getContext(), dialString)) {
202 //Is a country-specific exception to short codes as defined in TS 22.030, 6.5.3.2
204 } else if (isShortCode(dialString, phone)) {
205 // this may be a short code, as defined in TS 22.030, 6.5.3.2
206 ret = new GsmMmiCode(phone);
207 ret.dialingNumber = dialString;
214 newNetworkInitiatedUssd (String ussdMessage,
215 boolean isUssdRequest, GSMPhone phone) {
218 ret = new GsmMmiCode(phone);
220 ret.message = ussdMessage;
221 ret.isUssdRequest = isUssdRequest;
223 // If it's a request, set to PENDING so that it's cancelable.
225 ret.isPendingUSSD = true;
226 ret.state = State.PENDING;
228 ret.state = State.COMPLETE;
234 static GsmMmiCode newFromUssdUserInput(String ussdMessge, GSMPhone phone) {
235 GsmMmiCode ret = new GsmMmiCode(phone);
237 ret.message = ussdMessge;
238 ret.state = State.PENDING;
239 ret.isPendingUSSD = true;
244 //***** Private Class methods
246 /** make empty strings be null.
247 * Regexp returns empty strings for empty groups
249 private static String
250 makeEmptyNull (String s) {
251 if (s != null && s.length() == 0) return null;
256 /** returns true of the string is empty or null */
257 private static boolean
258 isEmptyOrNull(CharSequence s) {
259 return s == null || (s.length() == 0);
264 scToCallForwardReason(String sc) {
266 throw new RuntimeException ("invalid call forward sc");
269 if (sc.equals(SC_CF_All)) {
270 return CommandsInterface.CF_REASON_ALL;
271 } else if (sc.equals(SC_CFU)) {
272 return CommandsInterface.CF_REASON_UNCONDITIONAL;
273 } else if (sc.equals(SC_CFB)) {
274 return CommandsInterface.CF_REASON_BUSY;
275 } else if (sc.equals(SC_CFNR)) {
276 return CommandsInterface.CF_REASON_NOT_REACHABLE;
277 } else if (sc.equals(SC_CFNRy)) {
278 return CommandsInterface.CF_REASON_NO_REPLY;
279 } else if (sc.equals(SC_CF_All_Conditional)) {
280 return CommandsInterface.CF_REASON_ALL_CONDITIONAL;
282 throw new RuntimeException ("invalid call forward sc");
287 siToServiceClass(String si) {
288 if (si == null || si.length() == 0) {
289 return SERVICE_CLASS_NONE;
291 // NumberFormatException should cause MMI fail
292 int serviceCode = Integer.parseInt(si, 10);
294 switch (serviceCode) {
295 case 10: return SERVICE_CLASS_SMS + SERVICE_CLASS_FAX + SERVICE_CLASS_VOICE;
296 case 11: return SERVICE_CLASS_VOICE;
297 case 12: return SERVICE_CLASS_SMS + SERVICE_CLASS_FAX;
298 case 13: return SERVICE_CLASS_FAX;
300 case 16: return SERVICE_CLASS_SMS;
302 case 19: return SERVICE_CLASS_FAX + SERVICE_CLASS_VOICE;
305 From TS 22.030 Annex C:
306 "All GPRS bearer services" are not included in "All tele and bearer services"
307 and "All bearer services"."
308 ....so SERVICE_CLASS_DATA, which (according to 27.007) includes GPRS
310 case 20: return SERVICE_CLASS_DATA_ASYNC + SERVICE_CLASS_DATA_SYNC;
312 case 21: return SERVICE_CLASS_PAD + SERVICE_CLASS_DATA_ASYNC;
313 case 22: return SERVICE_CLASS_PACKET + SERVICE_CLASS_DATA_SYNC;
314 case 24: return SERVICE_CLASS_DATA_SYNC;
315 case 25: return SERVICE_CLASS_DATA_ASYNC;
316 case 26: return SERVICE_CLASS_DATA_SYNC + SERVICE_CLASS_VOICE;
317 case 99: return SERVICE_CLASS_PACKET;
320 throw new RuntimeException("unsupported MMI service code " + si);
326 siToTime (String si) {
327 if (si == null || si.length() == 0) {
330 // NumberFormatException should cause MMI fail
331 return Integer.parseInt(si, 10);
336 isServiceCodeCallForwarding(String sc) {
339 || sc.equals(SC_CFB) || sc.equals(SC_CFNRy)
340 || sc.equals(SC_CFNR) || sc.equals(SC_CF_All)
341 || sc.equals(SC_CF_All_Conditional));
345 isServiceCodeCallBarring(String sc) {
348 || sc.equals(SC_BAOIC)
349 || sc.equals(SC_BAOICxH)
350 || sc.equals(SC_BAIC)
351 || sc.equals(SC_BAICr)
352 || sc.equals(SC_BA_ALL)
353 || sc.equals(SC_BA_MO)
354 || sc.equals(SC_BA_MT));
358 scToBarringFacility(String sc) {
360 throw new RuntimeException ("invalid call barring sc");
363 if (sc.equals(SC_BAOC)) {
364 return CommandsInterface.CB_FACILITY_BAOC;
365 } else if (sc.equals(SC_BAOIC)) {
366 return CommandsInterface.CB_FACILITY_BAOIC;
367 } else if (sc.equals(SC_BAOICxH)) {
368 return CommandsInterface.CB_FACILITY_BAOICxH;
369 } else if (sc.equals(SC_BAIC)) {
370 return CommandsInterface.CB_FACILITY_BAIC;
371 } else if (sc.equals(SC_BAICr)) {
372 return CommandsInterface.CB_FACILITY_BAICr;
373 } else if (sc.equals(SC_BA_ALL)) {
374 return CommandsInterface.CB_FACILITY_BA_ALL;
375 } else if (sc.equals(SC_BA_MO)) {
376 return CommandsInterface.CB_FACILITY_BA_MO;
377 } else if (sc.equals(SC_BA_MT)) {
378 return CommandsInterface.CB_FACILITY_BA_MT;
380 throw new RuntimeException ("invalid call barring sc");
386 GsmMmiCode (GSMPhone phone) {
387 // The telephony unit-test cases may create GsmMmiCode's
388 // in secondary threads
389 super(phone.getHandler().getLooper());
391 this.context = phone.getContext();
394 //***** MmiCode implementation
406 // inherited javadoc suffices
409 // Complete or failed cannot be cancelled
410 if (state == State.COMPLETE || state == State.FAILED) {
414 state = State.CANCELLED;
418 * There can only be one pending USSD session, so tell the radio to
421 phone.mCM.cancelPendingUssd(obtainMessage(EVENT_USSD_CANCEL_COMPLETE, this));
424 * Don't call phone.onMMIDone here; wait for CANCEL_COMPLETE notice
428 // TODO in cases other than USSD, it would be nice to cancel
429 // the pending radio operation. This requires RIL cancellation
430 // support, which does not presently exist.
432 phone.onMMIDone (this);
437 public boolean isCancelable() {
438 /* Can only cancel pending USSD sessions. */
439 return isPendingUSSD;
442 //***** Instance Methods
444 /** Does this dial string contain a structured or unstructured MMI code? */
447 return poundString != null;
450 /* Is this a 1 or 2 digit "short code" as defined in TS 22.030 sec 6.5.3.2? */
453 return poundString == null
454 && dialingNumber != null && dialingNumber.length() <= 2;
458 static private boolean
459 isTwoDigitShortCode(Context context, String dialString) {
460 Log.d(LOG_TAG, "isTwoDigitShortCode");
462 if (dialString == null || dialString.length() != 2) return false;
464 if (sTwoDigitNumberPattern == null) {
465 sTwoDigitNumberPattern = context.getResources().getStringArray(
466 com.android.internal.R.array.config_twoDigitNumberPattern);
469 for (String dialnumber : sTwoDigitNumberPattern) {
470 Log.d(LOG_TAG, "Two Digit Number Pattern " + dialnumber);
471 if (dialString.equals(dialnumber)) {
472 Log.d(LOG_TAG, "Two Digit Number Pattern -true");
476 Log.d(LOG_TAG, "Two Digit Number Pattern -false");
481 * Helper function for newFromDialString. Returns true if dialString appears
482 * to be a short code AND conditions are correct for it to be treated as
485 static private boolean isShortCode(String dialString, GSMPhone phone) {
486 // Refer to TS 22.030 Figure 3.5.3.2:
487 if (dialString == null) {
491 // Illegal dial string characters will give a ZERO length.
492 // At this point we do not want to crash as any application with
493 // call privileges may send a non dial string.
494 // It return false as when the dialString is equal to NULL.
495 if (dialString.length() == 0) {
499 if (PhoneNumberUtils.isLocalEmergencyNumber(dialString, phone.getContext())) {
502 return isShortCodeUSSD(dialString, phone);
507 * Helper function for isShortCode. Returns true if dialString appears to be
508 * a short code and it is a USSD structure
510 * According to the 3PGG TS 22.030 specification Figure 3.5.3.2: A 1 or 2
511 * digit "short code" is treated as USSD if it is entered while on a call or
512 * does not satisfy the condition (exactly 2 digits && starts with '1'), there
513 * are however exceptions to this rule (see below)
515 * Exception (1) to Call initiation is: If the user of the device is already in a call
516 * and enters a Short String without any #-key at the end and the length of the Short String is
517 * equal or less then the MAX_LENGTH_SHORT_CODE [constant that is equal to 2]
519 * The phone shall initiate a USSD/SS commands.
521 * Exception (2) to Call initiation is: If the user of the device enters one
522 * Digit followed by the #-key. This rule defines this String as the
523 * #-String which is a USSD/SS command.
525 * The phone shall initiate a USSD/SS command.
527 static private boolean isShortCodeUSSD(String dialString, GSMPhone phone) {
528 if (dialString != null) {
529 if (phone.isInCall()) {
530 // The maximum length of a Short Code (aka Short String) is 2
531 if (dialString.length() <= MAX_LENGTH_SHORT_CODE) {
536 // The maximum length of a Short Code (aka Short String) is 2
537 if (dialString.length() <= MAX_LENGTH_SHORT_CODE) {
538 if (dialString.charAt(dialString.length() - 1) == END_OF_USSD_COMMAND) {
547 * @return true if the Service Code is PIN/PIN2/PUK/PUK2-related
549 boolean isPinCommand() {
550 return sc != null && (sc.equals(SC_PIN) || sc.equals(SC_PIN2)
551 || sc.equals(SC_PUK) || sc.equals(SC_PUK2));
555 * See TS 22.030 Annex B.
556 * In temporary mode, to suppress CLIR for a single call, enter:
557 * " * 31 # [called number] SEND "
558 * In temporary mode, to invoke CLIR for a single call enter:
559 * " # 31 # [called number] SEND "
562 isTemporaryModeCLIR() {
563 return sc != null && sc.equals(SC_CLIR) && dialingNumber != null
564 && (isActivate() || isDeactivate());
568 * returns CommandsInterface.CLIR_*
569 * See also isTemporaryModeCLIR()
573 if (sc != null && sc.equals(SC_CLIR)) {
575 return CommandsInterface.CLIR_SUPPRESSION;
576 } else if (isDeactivate()) {
577 return CommandsInterface.CLIR_INVOCATION;
581 return CommandsInterface.CLIR_DEFAULT;
584 boolean isActivate() {
585 return action != null && action.equals(ACTION_ACTIVATE);
588 boolean isDeactivate() {
589 return action != null && action.equals(ACTION_DEACTIVATE);
592 boolean isInterrogate() {
593 return action != null && action.equals(ACTION_INTERROGATE);
596 boolean isRegister() {
597 return action != null && action.equals(ACTION_REGISTER);
600 boolean isErasure() {
601 return action != null && action.equals(ACTION_ERASURE);
605 * Returns true if this is a USSD code that's been submitted to the
606 * network...eg, after processCode() is called
608 public boolean isPendingUSSD() {
609 return isPendingUSSD;
612 public boolean isUssdRequest() {
613 return isUssdRequest;
616 /** Process a MMI code or short code...anything that isn't a dialing number */
621 Log.d(LOG_TAG, "isShortCode");
622 // These just get treated as USSD.
623 sendUssd(dialingNumber);
624 } else if (dialingNumber != null) {
625 // We should have no dialing numbers here
626 throw new RuntimeException ("Invalid or Unsupported MMI Code");
627 } else if (sc != null && sc.equals(SC_CLIP)) {
628 Log.d(LOG_TAG, "is CLIP");
629 if (isInterrogate()) {
631 obtainMessage(EVENT_QUERY_COMPLETE, this));
633 throw new RuntimeException ("Invalid or Unsupported MMI Code");
635 } else if (sc != null && sc.equals(SC_CLIR)) {
636 Log.d(LOG_TAG, "is CLIR");
638 phone.mCM.setCLIR(CommandsInterface.CLIR_INVOCATION,
639 obtainMessage(EVENT_SET_COMPLETE, this));
640 } else if (isDeactivate()) {
641 phone.mCM.setCLIR(CommandsInterface.CLIR_SUPPRESSION,
642 obtainMessage(EVENT_SET_COMPLETE, this));
643 } else if (isInterrogate()) {
645 obtainMessage(EVENT_GET_CLIR_COMPLETE, this));
647 throw new RuntimeException ("Invalid or Unsupported MMI Code");
649 } else if (isServiceCodeCallForwarding(sc)) {
650 Log.d(LOG_TAG, "is CF");
652 String dialingNumber = sia;
653 int serviceClass = siToServiceClass(sib);
654 int reason = scToCallForwardReason(sc);
655 int time = siToTime(sic);
657 if (isInterrogate()) {
658 phone.mCM.queryCallForwardStatus(
659 reason, serviceClass, dialingNumber,
660 obtainMessage(EVENT_QUERY_CF_COMPLETE, this));
665 cfAction = CommandsInterface.CF_ACTION_ENABLE;
666 } else if (isDeactivate()) {
667 cfAction = CommandsInterface.CF_ACTION_DISABLE;
668 } else if (isRegister()) {
669 cfAction = CommandsInterface.CF_ACTION_REGISTRATION;
670 } else if (isErasure()) {
671 cfAction = CommandsInterface.CF_ACTION_ERASURE;
673 throw new RuntimeException ("invalid action");
676 int isSettingUnconditionalVoice =
677 (((reason == CommandsInterface.CF_REASON_UNCONDITIONAL) ||
678 (reason == CommandsInterface.CF_REASON_ALL)) &&
679 (((serviceClass & CommandsInterface.SERVICE_CLASS_VOICE) != 0) ||
680 (serviceClass == CommandsInterface.SERVICE_CLASS_NONE))) ? 1 : 0;
682 int isEnableDesired =
683 ((cfAction == CommandsInterface.CF_ACTION_ENABLE) ||
684 (cfAction == CommandsInterface.CF_ACTION_REGISTRATION)) ? 1 : 0;
686 Log.d(LOG_TAG, "is CF setCallForward");
687 phone.mCM.setCallForward(cfAction, reason, serviceClass,
688 dialingNumber, time, obtainMessage(
689 EVENT_SET_CFF_COMPLETE,
690 isSettingUnconditionalVoice,
691 isEnableDesired, this));
693 } else if (isServiceCodeCallBarring(sc)) {
695 // sib = basic service group
697 String password = sia;
698 int serviceClass = siToServiceClass(sib);
699 String facility = scToBarringFacility(sc);
701 if (isInterrogate()) {
702 phone.mCM.queryFacilityLock(facility, password,
703 serviceClass, obtainMessage(EVENT_QUERY_COMPLETE, this));
704 } else if (isActivate() || isDeactivate()) {
705 phone.mCM.setFacilityLock(facility, isActivate(), password,
706 serviceClass, obtainMessage(EVENT_SET_COMPLETE, this));
708 throw new RuntimeException ("Invalid or Unsupported MMI Code");
711 } else if (sc != null && sc.equals(SC_PWD)) {
719 if (isActivate() || isRegister()) {
720 // Even though ACTIVATE is acceptable, this is really termed a REGISTER
721 action = ACTION_REGISTER;
724 // If sc was not specified, treat it as BA_ALL.
725 facility = CommandsInterface.CB_FACILITY_BA_ALL;
727 facility = scToBarringFacility(sia);
729 if (newPwd.equals(pwd)) {
730 phone.mCM.changeBarringPassword(facility, oldPwd,
731 newPwd, obtainMessage(EVENT_SET_COMPLETE, this));
733 // password mismatch; return error
734 handlePasswordError(com.android.internal.R.string.passwordIncorrect);
737 throw new RuntimeException ("Invalid or Unsupported MMI Code");
740 } else if (sc != null && sc.equals(SC_WAIT)) {
741 // sia = basic service group
742 int serviceClass = siToServiceClass(sia);
744 if (isActivate() || isDeactivate()) {
745 phone.mCM.setCallWaiting(isActivate(), serviceClass,
746 obtainMessage(EVENT_SET_COMPLETE, this));
747 } else if (isInterrogate()) {
748 phone.mCM.queryCallWaiting(serviceClass,
749 obtainMessage(EVENT_QUERY_COMPLETE, this));
751 throw new RuntimeException ("Invalid or Unsupported MMI Code");
753 } else if (isPinCommand()) {
754 // sia = old PIN or PUK
757 String oldPinOrPuk = sia;
759 int pinLen = newPin.length();
761 if (!newPin.equals(sic)) {
762 // password mismatch; return error
763 handlePasswordError(com.android.internal.R.string.mismatchPin);
764 } else if (pinLen < 4 || pinLen > 8 ) {
766 handlePasswordError(com.android.internal.R.string.invalidPin);
767 } else if (sc.equals(SC_PIN) &&
768 phone.mIccCard.getState() == SimCard.State.PUK_REQUIRED ) {
770 handlePasswordError(com.android.internal.R.string.needPuk);
773 if (sc.equals(SC_PIN)) {
774 phone.mCM.changeIccPin(oldPinOrPuk, newPin,
775 obtainMessage(EVENT_SET_COMPLETE, this));
776 } else if (sc.equals(SC_PIN2)) {
777 phone.mCM.changeIccPin2(oldPinOrPuk, newPin,
778 obtainMessage(EVENT_SET_COMPLETE, this));
779 } else if (sc.equals(SC_PUK)) {
780 phone.mCM.supplyIccPuk(oldPinOrPuk, newPin,
781 obtainMessage(EVENT_SET_COMPLETE, this));
782 } else if (sc.equals(SC_PUK2)) {
783 phone.mCM.supplyIccPuk2(oldPinOrPuk, newPin,
784 obtainMessage(EVENT_SET_COMPLETE, this));
788 throw new RuntimeException ("Invalid or Unsupported MMI Code");
790 } else if (poundString != null) {
791 sendUssd(poundString);
793 throw new RuntimeException ("Invalid or Unsupported MMI Code");
795 } catch (RuntimeException exc) {
796 state = State.FAILED;
797 message = context.getText(com.android.internal.R.string.mmiError);
798 phone.onMMIDone(this);
802 private void handlePasswordError(int res) {
803 state = State.FAILED;
804 StringBuilder sb = new StringBuilder(getScString());
806 sb.append(context.getText(res));
808 phone.onMMIDone(this);
812 * Called from GSMPhone
814 * An unsolicited USSD NOTIFY or REQUEST has come in matching
815 * up with this pending USSD request
817 * Note: If REQUEST, this exchange is complete, but the session remains
818 * active (ie, the network expects user input).
821 onUssdFinished(String ussdMessage, boolean isUssdRequest) {
822 if (state == State.PENDING) {
823 if (ussdMessage == null) {
824 message = context.getText(com.android.internal.R.string.mmiComplete);
826 message = ussdMessage;
828 this.isUssdRequest = isUssdRequest;
829 // If it's a request, leave it PENDING so that it's cancelable.
830 if (!isUssdRequest) {
831 state = State.COMPLETE;
834 phone.onMMIDone(this);
839 * Called from GSMPhone
841 * The radio has reset, and this is still pending
845 onUssdFinishedError() {
846 if (state == State.PENDING) {
847 state = State.FAILED;
848 message = context.getText(com.android.internal.R.string.mmiError);
850 phone.onMMIDone(this);
854 void sendUssd(String ussdMessage) {
855 // Treat this as a USSD string
856 isPendingUSSD = true;
858 // Note that unlike most everything else, the USSD complete
859 // response does not complete this MMI code...we wait for
860 // an unsolicited USSD "Notify" or "Request".
861 // The matching up of this is done in GSMPhone.
863 phone.mCM.sendUSSD(ussdMessage,
864 obtainMessage(EVENT_USSD_COMPLETE, this));
867 /** Called from GSMPhone.handleMessage; not a Handler subclass */
869 handleMessage (Message msg) {
873 case EVENT_SET_COMPLETE:
874 ar = (AsyncResult) (msg.obj);
879 case EVENT_SET_CFF_COMPLETE:
880 ar = (AsyncResult) (msg.obj);
883 * msg.arg1 = 1 means to set unconditional voice call forwarding
884 * msg.arg2 = 1 means to enable voice call forwarding
886 if ((ar.exception == null) && (msg.arg1 == 1)) {
887 boolean cffEnabled = (msg.arg2 == 1);
888 phone.mIccRecords.setVoiceCallForwardingFlag(1, cffEnabled);
894 case EVENT_GET_CLIR_COMPLETE:
895 ar = (AsyncResult) (msg.obj);
896 onGetClirComplete(ar);
899 case EVENT_QUERY_CF_COMPLETE:
900 ar = (AsyncResult) (msg.obj);
901 onQueryCfComplete(ar);
904 case EVENT_QUERY_COMPLETE:
905 ar = (AsyncResult) (msg.obj);
909 case EVENT_USSD_COMPLETE:
910 ar = (AsyncResult) (msg.obj);
912 if (ar.exception != null) {
913 state = State.FAILED;
914 message = getErrorMessage(ar);
916 phone.onMMIDone(this);
919 // Note that unlike most everything else, the USSD complete
920 // response does not complete this MMI code...we wait for
921 // an unsolicited USSD "Notify" or "Request".
922 // The matching up of this is done in GSMPhone.
926 case EVENT_USSD_CANCEL_COMPLETE:
927 phone.onMMIDone(this);
931 //***** Private instance methods
933 private CharSequence getErrorMessage(AsyncResult ar) {
935 if (ar.exception instanceof CommandException) {
936 CommandException.Error err = ((CommandException)(ar.exception)).getCommandError();
937 if (err == CommandException.Error.FDN_CHECK_FAILURE) {
938 Log.i(LOG_TAG, "FDN_CHECK_FAILURE");
939 return context.getText(com.android.internal.R.string.mmiFdnError);
943 return context.getText(com.android.internal.R.string.mmiError);
946 private CharSequence getScString() {
948 if (isServiceCodeCallBarring(sc)) {
949 return context.getText(com.android.internal.R.string.BaMmi);
950 } else if (isServiceCodeCallForwarding(sc)) {
951 return context.getText(com.android.internal.R.string.CfMmi);
952 } else if (sc.equals(SC_CLIP)) {
953 return context.getText(com.android.internal.R.string.ClipMmi);
954 } else if (sc.equals(SC_CLIR)) {
955 return context.getText(com.android.internal.R.string.ClirMmi);
956 } else if (sc.equals(SC_PWD)) {
957 return context.getText(com.android.internal.R.string.PwdMmi);
958 } else if (sc.equals(SC_WAIT)) {
959 return context.getText(com.android.internal.R.string.CwMmi);
960 } else if (isPinCommand()) {
961 return context.getText(com.android.internal.R.string.PinMmi);
969 onSetComplete(AsyncResult ar){
970 StringBuilder sb = new StringBuilder(getScString());
973 if (ar.exception != null) {
974 state = State.FAILED;
975 if (ar.exception instanceof CommandException) {
976 CommandException.Error err = ((CommandException)(ar.exception)).getCommandError();
977 if (err == CommandException.Error.PASSWORD_INCORRECT) {
978 if (isPinCommand()) {
979 // look specifically for the PUK commands and adjust
980 // the message accordingly.
981 if (sc.equals(SC_PUK) || sc.equals(SC_PUK2)) {
982 sb.append(context.getText(
983 com.android.internal.R.string.badPuk));
985 sb.append(context.getText(
986 com.android.internal.R.string.badPin));
989 sb.append(context.getText(
990 com.android.internal.R.string.passwordIncorrect));
992 } else if (err == CommandException.Error.SIM_PUK2) {
993 sb.append(context.getText(
994 com.android.internal.R.string.badPin));
996 sb.append(context.getText(
997 com.android.internal.R.string.needPuk2));
998 } else if (err == CommandException.Error.FDN_CHECK_FAILURE) {
999 Log.i(LOG_TAG, "FDN_CHECK_FAILURE");
1000 sb.append(context.getText(com.android.internal.R.string.mmiFdnError));
1002 sb.append(context.getText(
1003 com.android.internal.R.string.mmiError));
1006 sb.append(context.getText(
1007 com.android.internal.R.string.mmiError));
1009 } else if (isActivate()) {
1010 state = State.COMPLETE;
1011 sb.append(context.getText(
1012 com.android.internal.R.string.serviceEnabled));
1013 // Record CLIR setting
1014 if (sc.equals(SC_CLIR)) {
1015 phone.saveClirSetting(CommandsInterface.CLIR_INVOCATION);
1017 } else if (isDeactivate()) {
1018 state = State.COMPLETE;
1019 sb.append(context.getText(
1020 com.android.internal.R.string.serviceDisabled));
1021 // Record CLIR setting
1022 if (sc.equals(SC_CLIR)) {
1023 phone.saveClirSetting(CommandsInterface.CLIR_SUPPRESSION);
1025 } else if (isRegister()) {
1026 state = State.COMPLETE;
1027 sb.append(context.getText(
1028 com.android.internal.R.string.serviceRegistered));
1029 } else if (isErasure()) {
1030 state = State.COMPLETE;
1031 sb.append(context.getText(
1032 com.android.internal.R.string.serviceErased));
1034 state = State.FAILED;
1035 sb.append(context.getText(
1036 com.android.internal.R.string.mmiError));
1040 phone.onMMIDone(this);
1044 onGetClirComplete(AsyncResult ar) {
1045 StringBuilder sb = new StringBuilder(getScString());
1048 if (ar.exception != null) {
1049 state = State.FAILED;
1050 sb.append(getErrorMessage(ar));
1054 clirArgs = (int[])ar.result;
1056 // the 'm' parameter from TS 27.007 7.7
1057 switch (clirArgs[1]) {
1058 case 0: // CLIR not provisioned
1059 sb.append(context.getText(
1060 com.android.internal.R.string.serviceNotProvisioned));
1061 state = State.COMPLETE;
1064 case 1: // CLIR provisioned in permanent mode
1065 sb.append(context.getText(
1066 com.android.internal.R.string.CLIRPermanent));
1067 state = State.COMPLETE;
1070 case 2: // unknown (e.g. no network, etc.)
1071 sb.append(context.getText(
1072 com.android.internal.R.string.mmiError));
1073 state = State.FAILED;
1076 case 3: // CLIR temporary mode presentation restricted
1078 // the 'n' parameter from TS 27.007 7.7
1079 switch (clirArgs[0]) {
1082 sb.append(context.getText(
1083 com.android.internal.R.string.CLIRDefaultOnNextCallOn));
1085 case 1: // CLIR invocation
1086 sb.append(context.getText(
1087 com.android.internal.R.string.CLIRDefaultOnNextCallOn));
1089 case 2: // CLIR suppression
1090 sb.append(context.getText(
1091 com.android.internal.R.string.CLIRDefaultOnNextCallOff));
1094 state = State.COMPLETE;
1097 case 4: // CLIR temporary mode presentation allowed
1098 // the 'n' parameter from TS 27.007 7.7
1099 switch (clirArgs[0]) {
1102 sb.append(context.getText(
1103 com.android.internal.R.string.CLIRDefaultOffNextCallOff));
1105 case 1: // CLIR invocation
1106 sb.append(context.getText(
1107 com.android.internal.R.string.CLIRDefaultOffNextCallOn));
1109 case 2: // CLIR suppression
1110 sb.append(context.getText(
1111 com.android.internal.R.string.CLIRDefaultOffNextCallOff));
1115 state = State.COMPLETE;
1121 phone.onMMIDone(this);
1125 * @param serviceClass 1 bit of the service class bit vectory
1126 * @return String to be used for call forward query MMI response text.
1127 * Returns null if unrecognized
1130 private CharSequence
1131 serviceClassToCFString (int serviceClass) {
1132 switch (serviceClass) {
1133 case SERVICE_CLASS_VOICE:
1134 return context.getText(com.android.internal.R.string.serviceClassVoice);
1135 case SERVICE_CLASS_DATA:
1136 return context.getText(com.android.internal.R.string.serviceClassData);
1137 case SERVICE_CLASS_FAX:
1138 return context.getText(com.android.internal.R.string.serviceClassFAX);
1139 case SERVICE_CLASS_SMS:
1140 return context.getText(com.android.internal.R.string.serviceClassSMS);
1141 case SERVICE_CLASS_DATA_SYNC:
1142 return context.getText(com.android.internal.R.string.serviceClassDataSync);
1143 case SERVICE_CLASS_DATA_ASYNC:
1144 return context.getText(com.android.internal.R.string.serviceClassDataAsync);
1145 case SERVICE_CLASS_PACKET:
1146 return context.getText(com.android.internal.R.string.serviceClassPacket);
1147 case SERVICE_CLASS_PAD:
1148 return context.getText(com.android.internal.R.string.serviceClassPAD);
1155 /** one CallForwardInfo + serviceClassMask -> one line of text */
1156 private CharSequence
1157 makeCFQueryResultMessage(CallForwardInfo info, int serviceClassMask) {
1158 CharSequence template;
1159 String sources[] = {"{0}", "{1}", "{2}"};
1160 CharSequence destinations[] = new CharSequence[3];
1161 boolean needTimeTemplate;
1163 // CF_REASON_NO_REPLY also has a time value associated with
1164 // it. All others don't.
1167 (info.reason == CommandsInterface.CF_REASON_NO_REPLY);
1169 if (info.status == 1) {
1170 if (needTimeTemplate) {
1171 template = context.getText(
1172 com.android.internal.R.string.cfTemplateForwardedTime);
1174 template = context.getText(
1175 com.android.internal.R.string.cfTemplateForwarded);
1177 } else if (info.status == 0 && isEmptyOrNull(info.number)) {
1178 template = context.getText(
1179 com.android.internal.R.string.cfTemplateNotForwarded);
1180 } else { /* (info.status == 0) && !isEmptyOrNull(info.number) */
1181 // A call forward record that is not active but contains
1182 // a phone number is considered "registered"
1184 if (needTimeTemplate) {
1185 template = context.getText(
1186 com.android.internal.R.string.cfTemplateRegisteredTime);
1188 template = context.getText(
1189 com.android.internal.R.string.cfTemplateRegistered);
1193 // In the template (from strings.xmls)
1194 // {0} is one of "bearerServiceCode*"
1195 // {1} is dialing number
1196 // {2} is time in seconds
1198 destinations[0] = serviceClassToCFString(info.serviceClass & serviceClassMask);
1199 destinations[1] = PhoneNumberUtils.stringFromStringAndTOA(info.number, info.toa);
1200 destinations[2] = Integer.toString(info.timeSeconds);
1202 if (info.reason == CommandsInterface.CF_REASON_UNCONDITIONAL &&
1203 (info.serviceClass & serviceClassMask)
1204 == CommandsInterface.SERVICE_CLASS_VOICE) {
1205 boolean cffEnabled = (info.status == 1);
1206 phone.mIccRecords.setVoiceCallForwardingFlag(1, cffEnabled);
1209 return TextUtils.replace(template, sources, destinations);
1214 onQueryCfComplete(AsyncResult ar) {
1215 StringBuilder sb = new StringBuilder(getScString());
1218 if (ar.exception != null) {
1219 state = State.FAILED;
1220 sb.append(getErrorMessage(ar));
1222 CallForwardInfo infos[];
1224 infos = (CallForwardInfo[]) ar.result;
1226 if (infos.length == 0) {
1227 // Assume the default is not active
1228 sb.append(context.getText(com.android.internal.R.string.serviceDisabled));
1230 // Set unconditional CFF in SIM to false
1231 phone.mIccRecords.setVoiceCallForwardingFlag(1, false);
1234 SpannableStringBuilder tb = new SpannableStringBuilder();
1236 // Each bit in the service class gets its own result line
1237 // The service classes may be split up over multiple
1238 // CallForwardInfos. So, for each service class, find out
1239 // which CallForwardInfo represents it and then build
1240 // the response text based on that
1242 for (int serviceClassMask = 1
1243 ; serviceClassMask <= SERVICE_CLASS_MAX
1244 ; serviceClassMask <<= 1
1246 for (int i = 0, s = infos.length; i < s ; i++) {
1247 if ((serviceClassMask & infos[i].serviceClass) != 0) {
1248 tb.append(makeCFQueryResultMessage(infos[i],
1257 state = State.COMPLETE;
1261 phone.onMMIDone(this);
1266 onQueryComplete(AsyncResult ar) {
1267 StringBuilder sb = new StringBuilder(getScString());
1270 if (ar.exception != null) {
1271 state = State.FAILED;
1272 sb.append(getErrorMessage(ar));
1274 int[] ints = (int[])ar.result;
1276 if (ints.length != 0) {
1278 sb.append(context.getText(com.android.internal.R.string.serviceDisabled));
1279 } else if (sc.equals(SC_WAIT)) {
1280 // Call Waiting includes additional data in the response.
1281 sb.append(createQueryCallWaitingResultMessage(ints[1]));
1282 } else if (isServiceCodeCallBarring(sc)) {
1283 // ints[0] for Call Barring is a bit vector of services
1284 sb.append(createQueryCallBarringResultMessage(ints[0]));
1285 } else if (ints[0] == 1) {
1286 // for all other services, treat it as a boolean
1287 sb.append(context.getText(com.android.internal.R.string.serviceEnabled));
1289 sb.append(context.getText(com.android.internal.R.string.mmiError));
1292 sb.append(context.getText(com.android.internal.R.string.mmiError));
1294 state = State.COMPLETE;
1298 phone.onMMIDone(this);
1301 private CharSequence
1302 createQueryCallWaitingResultMessage(int serviceClass) {
1304 new StringBuilder(context.getText(com.android.internal.R.string.serviceEnabledFor));
1306 for (int classMask = 1
1307 ; classMask <= SERVICE_CLASS_MAX
1310 if ((classMask & serviceClass) != 0) {
1312 sb.append(serviceClassToCFString(classMask & serviceClass));
1317 private CharSequence
1318 createQueryCallBarringResultMessage(int serviceClass)
1320 StringBuilder sb = new StringBuilder(context.getText(com.android.internal.R.string.serviceEnabledFor));
1322 for (int classMask = 1
1323 ; classMask <= SERVICE_CLASS_MAX
1326 if ((classMask & serviceClass) != 0) {
1328 sb.append(serviceClassToCFString(classMask & serviceClass));
1335 * TODO: It would be nice to have a method here that can take in a dialstring and
1336 * figure out if there is an MMI code embedded within it. This code would replace
1337 * some of the string parsing functionality in the Phone App's
1338 * SpecialCharSequenceMgr class.
1342 public String toString() {
1343 StringBuilder sb = new StringBuilder("GsmMmiCode {");
1345 sb.append("State=" + getState());
1346 if (action != null) sb.append(" action=" + action);
1347 if (sc != null) sb.append(" sc=" + sc);
1348 if (sia != null) sb.append(" sia=" + sia);
1349 if (sib != null) sb.append(" sib=" + sib);
1350 if (sic != null) sb.append(" sic=" + sic);
1351 if (poundString != null) sb.append(" poundString=" + poundString);
1352 if (dialingNumber != null) sb.append(" dialingNumber=" + dialingNumber);
1353 if (pwd != null) sb.append(" pwd=" + pwd);
1355 return sb.toString();