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