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