GsmMmiCode.java revision e0e2ebb5a50e580cbe6957dcafb3495a2d0a27f2
1/* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.internal.telephony.gsm; 18 19import android.content.Context; 20import com.android.internal.telephony.*; 21 22import android.os.*; 23import android.telephony.PhoneNumberUtils; 24import android.text.SpannableStringBuilder; 25import android.text.TextUtils; 26import android.util.Log; 27 28import static com.android.internal.telephony.CommandsInterface.*; 29 30import java.util.regex.Pattern; 31import java.util.regex.Matcher; 32 33/** 34 * The motto for this file is: 35 * 36 * "NOTE: By using the # as a separator, most cases are expected to be unambiguous." 37 * -- TS 22.030 6.5.2 38 * 39 * {@hide} 40 * 41 */ 42public final class GsmMmiCode extends Handler implements MmiCode { 43 static final String LOG_TAG = "GSM"; 44 45 //***** Constants 46 47 // 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; 49 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 = '#'; 53 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 = "##"; 60 61 // Supp Service codes from TS 22.030 Annex B 62 63 //Called line presentation 64 static final String SC_CLIP = "30"; 65 static final String SC_CLIR = "31"; 66 67 // Call Forwarding 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"; 72 73 static final String SC_CF_All = "002"; 74 static final String SC_CF_All_Conditional = "004"; 75 76 // Call Waiting 77 static final String SC_WAIT = "43"; 78 79 // Call Barring 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"; 85 86 static final String SC_BA_ALL = "330"; 87 static final String SC_BA_MO = "333"; 88 static final String SC_BA_MT = "353"; 89 90 // Supp Service Password registration 91 static final String SC_PWD = "03"; 92 93 // PIN/PIN2/PUK/PUK2 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"; 98 99 //***** Event Constants 100 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; 108 109 //***** Instance Variables 110 111 GSMPhone phone; 112 Context context; 113 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 120 121 /** Set to true in processCode, not at newFromDialString time */ 122 private boolean isPendingUSSD; 123 124 private boolean isUssdRequest; 125 126 State state = State.PENDING; 127 CharSequence message; 128 129 //***** Class Variables 130 131 132 // See TS 22.030 6.5.2 "Structure of the MMI" 133 134 static Pattern sPatternSuppService = Pattern.compile( 135 "((\\*|#|\\*#|\\*\\*|##)(\\d{2,3})(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*))?)?)?)?#)([^#]*)"); 136/* 1 2 3 4 5 6 7 8 9 10 11 12 137 138 1 = Full string up to and including # 139 2 = action (activation/interrogation/registration/erasure) 140 3 = service code 141 5 = SIA 142 7 = SIB 143 9 = SIC 144 10 = dialing number which must not include #, e.g. *SCn*SI#DN format 145*/ 146 147 static final int MATCH_GROUP_POUND_STRING = 1; 148 149 static final int MATCH_GROUP_ACTION = 2; 150 //(activation/interrogation/registration/erasure) 151 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; 159 160 //***** Public Class methods 161 162 /** 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. 168 * 169 * If the dial string contains no MMI code, we return an instance with 170 * only "dialingNumber" set 171 * 172 * Please see flow chart in TS 22.030 6.5.3.2 173 */ 174 175 static GsmMmiCode 176 newFromDialString(String dialString, GSMPhone phone) { 177 Matcher m; 178 GsmMmiCode ret = null; 179 180 m = sPatternSuppService.matcher(dialString); 181 182 // Is this formatted like a standard supplementary service code? 183 if (m.matches()) { 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)); 193 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". 198 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 203 ret = null; 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; 208 } 209 210 return ret; 211 } 212 213 static GsmMmiCode 214 newNetworkInitiatedUssd (String ussdMessage, 215 boolean isUssdRequest, GSMPhone phone) { 216 GsmMmiCode ret; 217 218 ret = new GsmMmiCode(phone); 219 220 ret.message = ussdMessage; 221 ret.isUssdRequest = isUssdRequest; 222 223 // If it's a request, set to PENDING so that it's cancelable. 224 if (isUssdRequest) { 225 ret.isPendingUSSD = true; 226 ret.state = State.PENDING; 227 } else { 228 ret.state = State.COMPLETE; 229 } 230 231 return ret; 232 } 233 234 static GsmMmiCode newFromUssdUserInput(String ussdMessge, GSMPhone phone) { 235 GsmMmiCode ret = new GsmMmiCode(phone); 236 237 ret.message = ussdMessge; 238 ret.state = State.PENDING; 239 ret.isPendingUSSD = true; 240 241 return ret; 242 } 243 244 //***** Private Class methods 245 246 /** make empty strings be null. 247 * Regexp returns empty strings for empty groups 248 */ 249 private static String 250 makeEmptyNull (String s) { 251 if (s != null && s.length() == 0) return null; 252 253 return s; 254 } 255 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); 260 } 261 262 263 private static int 264 scToCallForwardReason(String sc) { 265 if (sc == null) { 266 throw new RuntimeException ("invalid call forward sc"); 267 } 268 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; 281 } else { 282 throw new RuntimeException ("invalid call forward sc"); 283 } 284 } 285 286 private static int 287 siToServiceClass(String si) { 288 if (si == null || si.length() == 0) { 289 return SERVICE_CLASS_NONE; 290 } else { 291 // NumberFormatException should cause MMI fail 292 int serviceCode = Integer.parseInt(si, 10); 293 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; 299 300 case 16: return SERVICE_CLASS_SMS; 301 302 case 19: return SERVICE_CLASS_FAX + SERVICE_CLASS_VOICE; 303/* 304 Note for code 20: 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 309*/ 310 case 20: return SERVICE_CLASS_DATA_ASYNC + SERVICE_CLASS_DATA_SYNC; 311 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; 318 319 default: 320 throw new RuntimeException("unsupported MMI service code " + si); 321 } 322 } 323 } 324 325 private static int 326 siToTime (String si) { 327 if (si == null || si.length() == 0) { 328 return 0; 329 } else { 330 // NumberFormatException should cause MMI fail 331 return Integer.parseInt(si, 10); 332 } 333 } 334 335 static boolean 336 isServiceCodeCallForwarding(String sc) { 337 return sc != null && 338 (sc.equals(SC_CFU) 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)); 342 } 343 344 static boolean 345 isServiceCodeCallBarring(String sc) { 346 return sc != null && 347 (sc.equals(SC_BAOC) 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)); 355 } 356 357 static String 358 scToBarringFacility(String sc) { 359 if (sc == null) { 360 throw new RuntimeException ("invalid call barring sc"); 361 } 362 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; 379 } else { 380 throw new RuntimeException ("invalid call barring sc"); 381 } 382 } 383 384 //***** Constructor 385 386 GsmMmiCode (GSMPhone phone) { 387 // The telephony unit-test cases may create GsmMmiCode's 388 // in secondary threads 389 super(phone.getHandler().getLooper()); 390 this.phone = phone; 391 this.context = phone.getContext(); 392 } 393 394 //***** MmiCode implementation 395 396 public State 397 getState() { 398 return state; 399 } 400 401 public CharSequence 402 getMessage() { 403 return message; 404 } 405 406 // inherited javadoc suffices 407 public void 408 cancel() { 409 // Complete or failed cannot be cancelled 410 if (state == State.COMPLETE || state == State.FAILED) { 411 return; 412 } 413 414 state = State.CANCELLED; 415 416 if (isPendingUSSD) { 417 /* 418 * There can only be one pending USSD session, so tell the radio to 419 * cancel it. 420 */ 421 phone.mCM.cancelPendingUssd(obtainMessage(EVENT_USSD_CANCEL_COMPLETE, this)); 422 423 /* 424 * Don't call phone.onMMIDone here; wait for CANCEL_COMPLETE notice 425 * from RIL. 426 */ 427 } else { 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. 431 432 phone.onMMIDone (this); 433 } 434 435 } 436 437 public boolean isCancelable() { 438 /* Can only cancel pending USSD sessions. */ 439 return isPendingUSSD; 440 } 441 442 //***** Instance Methods 443 444 /** Does this dial string contain a structured or unstructured MMI code? */ 445 boolean 446 isMMI() { 447 return poundString != null; 448 } 449 450 /* Is this a 1 or 2 digit "short code" as defined in TS 22.030 sec 6.5.3.2? */ 451 boolean 452 isShortCode() { 453 return poundString == null 454 && dialingNumber != null && dialingNumber.length() <= 2; 455 456 } 457 458 static private boolean 459 isTwoDigitShortCode(Context context, String dialString) { 460 Log.d(LOG_TAG, "isTwoDigitShortCode"); 461 462 if (dialString == null || dialString.length() != 2) return false; 463 464 if (sTwoDigitNumberPattern == null) { 465 sTwoDigitNumberPattern = context.getResources().getStringArray( 466 com.android.internal.R.array.config_twoDigitNumberPattern); 467 } 468 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"); 473 return true; 474 } 475 } 476 Log.d(LOG_TAG, "Two Digit Number Pattern -false"); 477 return false; 478 } 479 480 /** 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 483 * such. 484 */ 485 static private boolean isShortCode(String dialString, GSMPhone phone) { 486 // Refer to TS 22.030 Figure 3.5.3.2: 487 if (dialString == null) { 488 return false; 489 } 490 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) { 496 return false; 497 } 498 499 if (PhoneNumberUtils.isLocalEmergencyNumber(dialString, phone.getContext())) { 500 return false; 501 } else { 502 return isShortCodeUSSD(dialString, phone); 503 } 504 } 505 506 /** 507 * Helper function for isShortCode. Returns true if dialString appears to be 508 * a short code and it is a USSD structure 509 * 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) 514 * 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] 518 * 519 * The phone shall initiate a USSD/SS commands. 520 * 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. 524 * 525 * The phone shall initiate a USSD/SS command. 526 */ 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) { 532 return true; 533 } 534 } 535 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) { 539 return true; 540 } 541 } 542 } 543 return false; 544 } 545 546 /** 547 * @return true if the Service Code is PIN/PIN2/PUK/PUK2-related 548 */ 549 boolean isPinCommand() { 550 return sc != null && (sc.equals(SC_PIN) || sc.equals(SC_PIN2) 551 || sc.equals(SC_PUK) || sc.equals(SC_PUK2)); 552 } 553 554 /** 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 " 560 */ 561 boolean 562 isTemporaryModeCLIR() { 563 return sc != null && sc.equals(SC_CLIR) && dialingNumber != null 564 && (isActivate() || isDeactivate()); 565 } 566 567 /** 568 * returns CommandsInterface.CLIR_* 569 * See also isTemporaryModeCLIR() 570 */ 571 int 572 getCLIRMode() { 573 if (sc != null && sc.equals(SC_CLIR)) { 574 if (isActivate()) { 575 return CommandsInterface.CLIR_SUPPRESSION; 576 } else if (isDeactivate()) { 577 return CommandsInterface.CLIR_INVOCATION; 578 } 579 } 580 581 return CommandsInterface.CLIR_DEFAULT; 582 } 583 584 boolean isActivate() { 585 return action != null && action.equals(ACTION_ACTIVATE); 586 } 587 588 boolean isDeactivate() { 589 return action != null && action.equals(ACTION_DEACTIVATE); 590 } 591 592 boolean isInterrogate() { 593 return action != null && action.equals(ACTION_INTERROGATE); 594 } 595 596 boolean isRegister() { 597 return action != null && action.equals(ACTION_REGISTER); 598 } 599 600 boolean isErasure() { 601 return action != null && action.equals(ACTION_ERASURE); 602 } 603 604 /** 605 * Returns true if this is a USSD code that's been submitted to the 606 * network...eg, after processCode() is called 607 */ 608 public boolean isPendingUSSD() { 609 return isPendingUSSD; 610 } 611 612 public boolean isUssdRequest() { 613 return isUssdRequest; 614 } 615 616 /** Process a MMI code or short code...anything that isn't a dialing number */ 617 void 618 processCode () { 619 try { 620 if (isShortCode()) { 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()) { 630 phone.mCM.queryCLIP( 631 obtainMessage(EVENT_QUERY_COMPLETE, this)); 632 } else { 633 throw new RuntimeException ("Invalid or Unsupported MMI Code"); 634 } 635 } else if (sc != null && sc.equals(SC_CLIR)) { 636 Log.d(LOG_TAG, "is CLIR"); 637 if (isActivate()) { 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()) { 644 phone.mCM.getCLIR( 645 obtainMessage(EVENT_GET_CLIR_COMPLETE, this)); 646 } else { 647 throw new RuntimeException ("Invalid or Unsupported MMI Code"); 648 } 649 } else if (isServiceCodeCallForwarding(sc)) { 650 Log.d(LOG_TAG, "is CF"); 651 652 String dialingNumber = sia; 653 int serviceClass = siToServiceClass(sib); 654 int reason = scToCallForwardReason(sc); 655 int time = siToTime(sic); 656 657 if (isInterrogate()) { 658 phone.mCM.queryCallForwardStatus( 659 reason, serviceClass, dialingNumber, 660 obtainMessage(EVENT_QUERY_CF_COMPLETE, this)); 661 } else { 662 int cfAction; 663 664 if (isActivate()) { 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; 672 } else { 673 throw new RuntimeException ("invalid action"); 674 } 675 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; 681 682 int isEnableDesired = 683 ((cfAction == CommandsInterface.CF_ACTION_ENABLE) || 684 (cfAction == CommandsInterface.CF_ACTION_REGISTRATION)) ? 1 : 0; 685 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)); 692 } 693 } else if (isServiceCodeCallBarring(sc)) { 694 // sia = password 695 // sib = basic service group 696 697 String password = sia; 698 int serviceClass = siToServiceClass(sib); 699 String facility = scToBarringFacility(sc); 700 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)); 707 } else { 708 throw new RuntimeException ("Invalid or Unsupported MMI Code"); 709 } 710 711 } else if (sc != null && sc.equals(SC_PWD)) { 712 // sia = fac 713 // sib = old pwd 714 // sic = new pwd 715 // pwd = new pwd 716 String facility; 717 String oldPwd = sib; 718 String newPwd = sic; 719 if (isActivate() || isRegister()) { 720 // Even though ACTIVATE is acceptable, this is really termed a REGISTER 721 action = ACTION_REGISTER; 722 723 if (sia == null) { 724 // If sc was not specified, treat it as BA_ALL. 725 facility = CommandsInterface.CB_FACILITY_BA_ALL; 726 } else { 727 facility = scToBarringFacility(sia); 728 } 729 if (newPwd.equals(pwd)) { 730 phone.mCM.changeBarringPassword(facility, oldPwd, 731 newPwd, obtainMessage(EVENT_SET_COMPLETE, this)); 732 } else { 733 // password mismatch; return error 734 handlePasswordError(com.android.internal.R.string.passwordIncorrect); 735 } 736 } else { 737 throw new RuntimeException ("Invalid or Unsupported MMI Code"); 738 } 739 740 } else if (sc != null && sc.equals(SC_WAIT)) { 741 // sia = basic service group 742 int serviceClass = siToServiceClass(sia); 743 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)); 750 } else { 751 throw new RuntimeException ("Invalid or Unsupported MMI Code"); 752 } 753 } else if (isPinCommand()) { 754 // sia = old PIN or PUK 755 // sib = new PIN 756 // sic = new PIN 757 String oldPinOrPuk = sia; 758 String newPin = sib; 759 int pinLen = newPin.length(); 760 if (isRegister()) { 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 ) { 765 // invalid length 766 handlePasswordError(com.android.internal.R.string.invalidPin); 767 } else if (sc.equals(SC_PIN) && phone.getIccCard().getState() == 768 IccCardConstants.State.PUK_REQUIRED ) { 769 // Sim is puk-locked 770 handlePasswordError(com.android.internal.R.string.needPuk); 771 } else { 772 // pre-checks OK 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)); 785 } 786 } 787 } else { 788 throw new RuntimeException ("Invalid or Unsupported MMI Code"); 789 } 790 } else if (poundString != null) { 791 sendUssd(poundString); 792 } else { 793 throw new RuntimeException ("Invalid or Unsupported MMI Code"); 794 } 795 } catch (RuntimeException exc) { 796 state = State.FAILED; 797 message = context.getText(com.android.internal.R.string.mmiError); 798 phone.onMMIDone(this); 799 } 800 } 801 802 private void handlePasswordError(int res) { 803 state = State.FAILED; 804 StringBuilder sb = new StringBuilder(getScString()); 805 sb.append("\n"); 806 sb.append(context.getText(res)); 807 message = sb; 808 phone.onMMIDone(this); 809 } 810 811 /** 812 * Called from GSMPhone 813 * 814 * An unsolicited USSD NOTIFY or REQUEST has come in matching 815 * up with this pending USSD request 816 * 817 * Note: If REQUEST, this exchange is complete, but the session remains 818 * active (ie, the network expects user input). 819 */ 820 void 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); 825 } else { 826 message = ussdMessage; 827 } 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; 832 } 833 834 phone.onMMIDone(this); 835 } 836 } 837 838 /** 839 * Called from GSMPhone 840 * 841 * The radio has reset, and this is still pending 842 */ 843 844 void 845 onUssdFinishedError() { 846 if (state == State.PENDING) { 847 state = State.FAILED; 848 message = context.getText(com.android.internal.R.string.mmiError); 849 850 phone.onMMIDone(this); 851 } 852 } 853 854 void sendUssd(String ussdMessage) { 855 // Treat this as a USSD string 856 isPendingUSSD = true; 857 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. 862 863 phone.mCM.sendUSSD(ussdMessage, 864 obtainMessage(EVENT_USSD_COMPLETE, this)); 865 } 866 867 /** Called from GSMPhone.handleMessage; not a Handler subclass */ 868 public void 869 handleMessage (Message msg) { 870 AsyncResult ar; 871 872 switch (msg.what) { 873 case EVENT_SET_COMPLETE: 874 ar = (AsyncResult) (msg.obj); 875 876 onSetComplete(ar); 877 break; 878 879 case EVENT_SET_CFF_COMPLETE: 880 ar = (AsyncResult) (msg.obj); 881 882 /* 883 * msg.arg1 = 1 means to set unconditional voice call forwarding 884 * msg.arg2 = 1 means to enable voice call forwarding 885 */ 886 if ((ar.exception == null) && (msg.arg1 == 1)) { 887 boolean cffEnabled = (msg.arg2 == 1); 888 IccRecords r = phone.mIccRecords.get(); 889 if (r != null) { 890 r.setVoiceCallForwardingFlag(1, cffEnabled); 891 } 892 } 893 894 onSetComplete(ar); 895 break; 896 897 case EVENT_GET_CLIR_COMPLETE: 898 ar = (AsyncResult) (msg.obj); 899 onGetClirComplete(ar); 900 break; 901 902 case EVENT_QUERY_CF_COMPLETE: 903 ar = (AsyncResult) (msg.obj); 904 onQueryCfComplete(ar); 905 break; 906 907 case EVENT_QUERY_COMPLETE: 908 ar = (AsyncResult) (msg.obj); 909 onQueryComplete(ar); 910 break; 911 912 case EVENT_USSD_COMPLETE: 913 ar = (AsyncResult) (msg.obj); 914 915 if (ar.exception != null) { 916 state = State.FAILED; 917 message = getErrorMessage(ar); 918 919 phone.onMMIDone(this); 920 } 921 922 // Note that unlike most everything else, the USSD complete 923 // response does not complete this MMI code...we wait for 924 // an unsolicited USSD "Notify" or "Request". 925 // The matching up of this is done in GSMPhone. 926 927 break; 928 929 case EVENT_USSD_CANCEL_COMPLETE: 930 phone.onMMIDone(this); 931 break; 932 } 933 } 934 //***** Private instance methods 935 936 private CharSequence getErrorMessage(AsyncResult ar) { 937 938 if (ar.exception instanceof CommandException) { 939 CommandException.Error err = ((CommandException)(ar.exception)).getCommandError(); 940 if (err == CommandException.Error.FDN_CHECK_FAILURE) { 941 Log.i(LOG_TAG, "FDN_CHECK_FAILURE"); 942 return context.getText(com.android.internal.R.string.mmiFdnError); 943 } 944 } 945 946 return context.getText(com.android.internal.R.string.mmiError); 947 } 948 949 private CharSequence getScString() { 950 if (sc != null) { 951 if (isServiceCodeCallBarring(sc)) { 952 return context.getText(com.android.internal.R.string.BaMmi); 953 } else if (isServiceCodeCallForwarding(sc)) { 954 return context.getText(com.android.internal.R.string.CfMmi); 955 } else if (sc.equals(SC_CLIP)) { 956 return context.getText(com.android.internal.R.string.ClipMmi); 957 } else if (sc.equals(SC_CLIR)) { 958 return context.getText(com.android.internal.R.string.ClirMmi); 959 } else if (sc.equals(SC_PWD)) { 960 return context.getText(com.android.internal.R.string.PwdMmi); 961 } else if (sc.equals(SC_WAIT)) { 962 return context.getText(com.android.internal.R.string.CwMmi); 963 } else if (isPinCommand()) { 964 return context.getText(com.android.internal.R.string.PinMmi); 965 } 966 } 967 968 return ""; 969 } 970 971 private void 972 onSetComplete(AsyncResult ar){ 973 StringBuilder sb = new StringBuilder(getScString()); 974 sb.append("\n"); 975 976 if (ar.exception != null) { 977 state = State.FAILED; 978 if (ar.exception instanceof CommandException) { 979 CommandException.Error err = ((CommandException)(ar.exception)).getCommandError(); 980 if (err == CommandException.Error.PASSWORD_INCORRECT) { 981 if (isPinCommand()) { 982 // look specifically for the PUK commands and adjust 983 // the message accordingly. 984 if (sc.equals(SC_PUK) || sc.equals(SC_PUK2)) { 985 sb.append(context.getText( 986 com.android.internal.R.string.badPuk)); 987 } else { 988 sb.append(context.getText( 989 com.android.internal.R.string.badPin)); 990 } 991 } else { 992 sb.append(context.getText( 993 com.android.internal.R.string.passwordIncorrect)); 994 } 995 } else if (err == CommandException.Error.SIM_PUK2) { 996 sb.append(context.getText( 997 com.android.internal.R.string.badPin)); 998 sb.append("\n"); 999 sb.append(context.getText( 1000 com.android.internal.R.string.needPuk2)); 1001 } else if (err == CommandException.Error.FDN_CHECK_FAILURE) { 1002 Log.i(LOG_TAG, "FDN_CHECK_FAILURE"); 1003 sb.append(context.getText(com.android.internal.R.string.mmiFdnError)); 1004 } else { 1005 sb.append(context.getText( 1006 com.android.internal.R.string.mmiError)); 1007 } 1008 } else { 1009 sb.append(context.getText( 1010 com.android.internal.R.string.mmiError)); 1011 } 1012 } else if (isActivate()) { 1013 state = State.COMPLETE; 1014 sb.append(context.getText( 1015 com.android.internal.R.string.serviceEnabled)); 1016 // Record CLIR setting 1017 if (sc.equals(SC_CLIR)) { 1018 phone.saveClirSetting(CommandsInterface.CLIR_INVOCATION); 1019 } 1020 } else if (isDeactivate()) { 1021 state = State.COMPLETE; 1022 sb.append(context.getText( 1023 com.android.internal.R.string.serviceDisabled)); 1024 // Record CLIR setting 1025 if (sc.equals(SC_CLIR)) { 1026 phone.saveClirSetting(CommandsInterface.CLIR_SUPPRESSION); 1027 } 1028 } else if (isRegister()) { 1029 state = State.COMPLETE; 1030 sb.append(context.getText( 1031 com.android.internal.R.string.serviceRegistered)); 1032 } else if (isErasure()) { 1033 state = State.COMPLETE; 1034 sb.append(context.getText( 1035 com.android.internal.R.string.serviceErased)); 1036 } else { 1037 state = State.FAILED; 1038 sb.append(context.getText( 1039 com.android.internal.R.string.mmiError)); 1040 } 1041 1042 message = sb; 1043 phone.onMMIDone(this); 1044 } 1045 1046 private void 1047 onGetClirComplete(AsyncResult ar) { 1048 StringBuilder sb = new StringBuilder(getScString()); 1049 sb.append("\n"); 1050 1051 if (ar.exception != null) { 1052 state = State.FAILED; 1053 sb.append(getErrorMessage(ar)); 1054 } else { 1055 int clirArgs[]; 1056 1057 clirArgs = (int[])ar.result; 1058 1059 // the 'm' parameter from TS 27.007 7.7 1060 switch (clirArgs[1]) { 1061 case 0: // CLIR not provisioned 1062 sb.append(context.getText( 1063 com.android.internal.R.string.serviceNotProvisioned)); 1064 state = State.COMPLETE; 1065 break; 1066 1067 case 1: // CLIR provisioned in permanent mode 1068 sb.append(context.getText( 1069 com.android.internal.R.string.CLIRPermanent)); 1070 state = State.COMPLETE; 1071 break; 1072 1073 case 2: // unknown (e.g. no network, etc.) 1074 sb.append(context.getText( 1075 com.android.internal.R.string.mmiError)); 1076 state = State.FAILED; 1077 break; 1078 1079 case 3: // CLIR temporary mode presentation restricted 1080 1081 // the 'n' parameter from TS 27.007 7.7 1082 switch (clirArgs[0]) { 1083 default: 1084 case 0: // Default 1085 sb.append(context.getText( 1086 com.android.internal.R.string.CLIRDefaultOnNextCallOn)); 1087 break; 1088 case 1: // CLIR invocation 1089 sb.append(context.getText( 1090 com.android.internal.R.string.CLIRDefaultOnNextCallOn)); 1091 break; 1092 case 2: // CLIR suppression 1093 sb.append(context.getText( 1094 com.android.internal.R.string.CLIRDefaultOnNextCallOff)); 1095 break; 1096 } 1097 state = State.COMPLETE; 1098 break; 1099 1100 case 4: // CLIR temporary mode presentation allowed 1101 // the 'n' parameter from TS 27.007 7.7 1102 switch (clirArgs[0]) { 1103 default: 1104 case 0: // Default 1105 sb.append(context.getText( 1106 com.android.internal.R.string.CLIRDefaultOffNextCallOff)); 1107 break; 1108 case 1: // CLIR invocation 1109 sb.append(context.getText( 1110 com.android.internal.R.string.CLIRDefaultOffNextCallOn)); 1111 break; 1112 case 2: // CLIR suppression 1113 sb.append(context.getText( 1114 com.android.internal.R.string.CLIRDefaultOffNextCallOff)); 1115 break; 1116 } 1117 1118 state = State.COMPLETE; 1119 break; 1120 } 1121 } 1122 1123 message = sb; 1124 phone.onMMIDone(this); 1125 } 1126 1127 /** 1128 * @param serviceClass 1 bit of the service class bit vectory 1129 * @return String to be used for call forward query MMI response text. 1130 * Returns null if unrecognized 1131 */ 1132 1133 private CharSequence 1134 serviceClassToCFString (int serviceClass) { 1135 switch (serviceClass) { 1136 case SERVICE_CLASS_VOICE: 1137 return context.getText(com.android.internal.R.string.serviceClassVoice); 1138 case SERVICE_CLASS_DATA: 1139 return context.getText(com.android.internal.R.string.serviceClassData); 1140 case SERVICE_CLASS_FAX: 1141 return context.getText(com.android.internal.R.string.serviceClassFAX); 1142 case SERVICE_CLASS_SMS: 1143 return context.getText(com.android.internal.R.string.serviceClassSMS); 1144 case SERVICE_CLASS_DATA_SYNC: 1145 return context.getText(com.android.internal.R.string.serviceClassDataSync); 1146 case SERVICE_CLASS_DATA_ASYNC: 1147 return context.getText(com.android.internal.R.string.serviceClassDataAsync); 1148 case SERVICE_CLASS_PACKET: 1149 return context.getText(com.android.internal.R.string.serviceClassPacket); 1150 case SERVICE_CLASS_PAD: 1151 return context.getText(com.android.internal.R.string.serviceClassPAD); 1152 default: 1153 return null; 1154 } 1155 } 1156 1157 1158 /** one CallForwardInfo + serviceClassMask -> one line of text */ 1159 private CharSequence 1160 makeCFQueryResultMessage(CallForwardInfo info, int serviceClassMask) { 1161 CharSequence template; 1162 String sources[] = {"{0}", "{1}", "{2}"}; 1163 CharSequence destinations[] = new CharSequence[3]; 1164 boolean needTimeTemplate; 1165 1166 // CF_REASON_NO_REPLY also has a time value associated with 1167 // it. All others don't. 1168 1169 needTimeTemplate = 1170 (info.reason == CommandsInterface.CF_REASON_NO_REPLY); 1171 1172 if (info.status == 1) { 1173 if (needTimeTemplate) { 1174 template = context.getText( 1175 com.android.internal.R.string.cfTemplateForwardedTime); 1176 } else { 1177 template = context.getText( 1178 com.android.internal.R.string.cfTemplateForwarded); 1179 } 1180 } else if (info.status == 0 && isEmptyOrNull(info.number)) { 1181 template = context.getText( 1182 com.android.internal.R.string.cfTemplateNotForwarded); 1183 } else { /* (info.status == 0) && !isEmptyOrNull(info.number) */ 1184 // A call forward record that is not active but contains 1185 // a phone number is considered "registered" 1186 1187 if (needTimeTemplate) { 1188 template = context.getText( 1189 com.android.internal.R.string.cfTemplateRegisteredTime); 1190 } else { 1191 template = context.getText( 1192 com.android.internal.R.string.cfTemplateRegistered); 1193 } 1194 } 1195 1196 // In the template (from strings.xmls) 1197 // {0} is one of "bearerServiceCode*" 1198 // {1} is dialing number 1199 // {2} is time in seconds 1200 1201 destinations[0] = serviceClassToCFString(info.serviceClass & serviceClassMask); 1202 destinations[1] = PhoneNumberUtils.stringFromStringAndTOA(info.number, info.toa); 1203 destinations[2] = Integer.toString(info.timeSeconds); 1204 1205 if (info.reason == CommandsInterface.CF_REASON_UNCONDITIONAL && 1206 (info.serviceClass & serviceClassMask) 1207 == CommandsInterface.SERVICE_CLASS_VOICE) { 1208 boolean cffEnabled = (info.status == 1); 1209 IccRecords r = phone.mIccRecords.get(); 1210 if (r != null) { 1211 r.setVoiceCallForwardingFlag(1, cffEnabled); 1212 } 1213 } 1214 1215 return TextUtils.replace(template, sources, destinations); 1216 } 1217 1218 1219 private void 1220 onQueryCfComplete(AsyncResult ar) { 1221 StringBuilder sb = new StringBuilder(getScString()); 1222 sb.append("\n"); 1223 1224 if (ar.exception != null) { 1225 state = State.FAILED; 1226 sb.append(getErrorMessage(ar)); 1227 } else { 1228 CallForwardInfo infos[]; 1229 1230 infos = (CallForwardInfo[]) ar.result; 1231 1232 if (infos.length == 0) { 1233 // Assume the default is not active 1234 sb.append(context.getText(com.android.internal.R.string.serviceDisabled)); 1235 1236 // Set unconditional CFF in SIM to false 1237 IccRecords r = phone.mIccRecords.get(); 1238 if (r != null) { 1239 r.setVoiceCallForwardingFlag(1, false); 1240 } 1241 } else { 1242 1243 SpannableStringBuilder tb = new SpannableStringBuilder(); 1244 1245 // Each bit in the service class gets its own result line 1246 // The service classes may be split up over multiple 1247 // CallForwardInfos. So, for each service class, find out 1248 // which CallForwardInfo represents it and then build 1249 // the response text based on that 1250 1251 for (int serviceClassMask = 1 1252 ; serviceClassMask <= SERVICE_CLASS_MAX 1253 ; serviceClassMask <<= 1 1254 ) { 1255 for (int i = 0, s = infos.length; i < s ; i++) { 1256 if ((serviceClassMask & infos[i].serviceClass) != 0) { 1257 tb.append(makeCFQueryResultMessage(infos[i], 1258 serviceClassMask)); 1259 tb.append("\n"); 1260 } 1261 } 1262 } 1263 sb.append(tb); 1264 } 1265 1266 state = State.COMPLETE; 1267 } 1268 1269 message = sb; 1270 phone.onMMIDone(this); 1271 1272 } 1273 1274 private void 1275 onQueryComplete(AsyncResult ar) { 1276 StringBuilder sb = new StringBuilder(getScString()); 1277 sb.append("\n"); 1278 1279 if (ar.exception != null) { 1280 state = State.FAILED; 1281 sb.append(getErrorMessage(ar)); 1282 } else { 1283 int[] ints = (int[])ar.result; 1284 1285 if (ints.length != 0) { 1286 if (ints[0] == 0) { 1287 sb.append(context.getText(com.android.internal.R.string.serviceDisabled)); 1288 } else if (sc.equals(SC_WAIT)) { 1289 // Call Waiting includes additional data in the response. 1290 sb.append(createQueryCallWaitingResultMessage(ints[1])); 1291 } else if (isServiceCodeCallBarring(sc)) { 1292 // ints[0] for Call Barring is a bit vector of services 1293 sb.append(createQueryCallBarringResultMessage(ints[0])); 1294 } else if (ints[0] == 1) { 1295 // for all other services, treat it as a boolean 1296 sb.append(context.getText(com.android.internal.R.string.serviceEnabled)); 1297 } else { 1298 sb.append(context.getText(com.android.internal.R.string.mmiError)); 1299 } 1300 } else { 1301 sb.append(context.getText(com.android.internal.R.string.mmiError)); 1302 } 1303 state = State.COMPLETE; 1304 } 1305 1306 message = sb; 1307 phone.onMMIDone(this); 1308 } 1309 1310 private CharSequence 1311 createQueryCallWaitingResultMessage(int serviceClass) { 1312 StringBuilder sb = 1313 new StringBuilder(context.getText(com.android.internal.R.string.serviceEnabledFor)); 1314 1315 for (int classMask = 1 1316 ; classMask <= SERVICE_CLASS_MAX 1317 ; classMask <<= 1 1318 ) { 1319 if ((classMask & serviceClass) != 0) { 1320 sb.append("\n"); 1321 sb.append(serviceClassToCFString(classMask & serviceClass)); 1322 } 1323 } 1324 return sb; 1325 } 1326 private CharSequence 1327 createQueryCallBarringResultMessage(int serviceClass) 1328 { 1329 StringBuilder sb = new StringBuilder(context.getText(com.android.internal.R.string.serviceEnabledFor)); 1330 1331 for (int classMask = 1 1332 ; classMask <= SERVICE_CLASS_MAX 1333 ; classMask <<= 1 1334 ) { 1335 if ((classMask & serviceClass) != 0) { 1336 sb.append("\n"); 1337 sb.append(serviceClassToCFString(classMask & serviceClass)); 1338 } 1339 } 1340 return sb; 1341 } 1342 1343 /*** 1344 * TODO: It would be nice to have a method here that can take in a dialstring and 1345 * figure out if there is an MMI code embedded within it. This code would replace 1346 * some of the string parsing functionality in the Phone App's 1347 * SpecialCharSequenceMgr class. 1348 */ 1349 1350 @Override 1351 public String toString() { 1352 StringBuilder sb = new StringBuilder("GsmMmiCode {"); 1353 1354 sb.append("State=" + getState()); 1355 if (action != null) sb.append(" action=" + action); 1356 if (sc != null) sb.append(" sc=" + sc); 1357 if (sia != null) sb.append(" sia=" + sia); 1358 if (sib != null) sb.append(" sib=" + sib); 1359 if (sic != null) sb.append(" sic=" + sic); 1360 if (poundString != null) sb.append(" poundString=" + poundString); 1361 if (dialingNumber != null) sb.append(" dialingNumber=" + dialingNumber); 1362 if (pwd != null) sb.append(" pwd=" + pwd); 1363 sb.append("}"); 1364 return sb.toString(); 1365 } 1366} 1367