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) && 768 phone.getIccCard().getState() == IccCard.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 phone.mIccRecords.setVoiceCallForwardingFlag(1, cffEnabled); 889 } 890 891 onSetComplete(ar); 892 break; 893 894 case EVENT_GET_CLIR_COMPLETE: 895 ar = (AsyncResult) (msg.obj); 896 onGetClirComplete(ar); 897 break; 898 899 case EVENT_QUERY_CF_COMPLETE: 900 ar = (AsyncResult) (msg.obj); 901 onQueryCfComplete(ar); 902 break; 903 904 case EVENT_QUERY_COMPLETE: 905 ar = (AsyncResult) (msg.obj); 906 onQueryComplete(ar); 907 break; 908 909 case EVENT_USSD_COMPLETE: 910 ar = (AsyncResult) (msg.obj); 911 912 if (ar.exception != null) { 913 state = State.FAILED; 914 message = getErrorMessage(ar); 915 916 phone.onMMIDone(this); 917 } 918 919 // Note that unlike most everything else, the USSD complete 920 // response does not complete this MMI code...we wait for 921 // an unsolicited USSD "Notify" or "Request". 922 // The matching up of this is done in GSMPhone. 923 924 break; 925 926 case EVENT_USSD_CANCEL_COMPLETE: 927 phone.onMMIDone(this); 928 break; 929 } 930 } 931 //***** Private instance methods 932 933 private CharSequence getErrorMessage(AsyncResult ar) { 934 935 if (ar.exception instanceof CommandException) { 936 CommandException.Error err = ((CommandException)(ar.exception)).getCommandError(); 937 if (err == CommandException.Error.FDN_CHECK_FAILURE) { 938 Log.i(LOG_TAG, "FDN_CHECK_FAILURE"); 939 return context.getText(com.android.internal.R.string.mmiFdnError); 940 } 941 } 942 943 return context.getText(com.android.internal.R.string.mmiError); 944 } 945 946 private CharSequence getScString() { 947 if (sc != null) { 948 if (isServiceCodeCallBarring(sc)) { 949 return context.getText(com.android.internal.R.string.BaMmi); 950 } else if (isServiceCodeCallForwarding(sc)) { 951 return context.getText(com.android.internal.R.string.CfMmi); 952 } else if (sc.equals(SC_CLIP)) { 953 return context.getText(com.android.internal.R.string.ClipMmi); 954 } else if (sc.equals(SC_CLIR)) { 955 return context.getText(com.android.internal.R.string.ClirMmi); 956 } else if (sc.equals(SC_PWD)) { 957 return context.getText(com.android.internal.R.string.PwdMmi); 958 } else if (sc.equals(SC_WAIT)) { 959 return context.getText(com.android.internal.R.string.CwMmi); 960 } else if (isPinCommand()) { 961 return context.getText(com.android.internal.R.string.PinMmi); 962 } 963 } 964 965 return ""; 966 } 967 968 private void 969 onSetComplete(AsyncResult ar){ 970 StringBuilder sb = new StringBuilder(getScString()); 971 sb.append("\n"); 972 973 if (ar.exception != null) { 974 state = State.FAILED; 975 if (ar.exception instanceof CommandException) { 976 CommandException.Error err = ((CommandException)(ar.exception)).getCommandError(); 977 if (err == CommandException.Error.PASSWORD_INCORRECT) { 978 if (isPinCommand()) { 979 // look specifically for the PUK commands and adjust 980 // the message accordingly. 981 if (sc.equals(SC_PUK) || sc.equals(SC_PUK2)) { 982 sb.append(context.getText( 983 com.android.internal.R.string.badPuk)); 984 } else { 985 sb.append(context.getText( 986 com.android.internal.R.string.badPin)); 987 } 988 } else { 989 sb.append(context.getText( 990 com.android.internal.R.string.passwordIncorrect)); 991 } 992 } else if (err == CommandException.Error.SIM_PUK2) { 993 sb.append(context.getText( 994 com.android.internal.R.string.badPin)); 995 sb.append("\n"); 996 sb.append(context.getText( 997 com.android.internal.R.string.needPuk2)); 998 } else if (err == CommandException.Error.FDN_CHECK_FAILURE) { 999 Log.i(LOG_TAG, "FDN_CHECK_FAILURE"); 1000 sb.append(context.getText(com.android.internal.R.string.mmiFdnError)); 1001 } else { 1002 sb.append(context.getText( 1003 com.android.internal.R.string.mmiError)); 1004 } 1005 } else { 1006 sb.append(context.getText( 1007 com.android.internal.R.string.mmiError)); 1008 } 1009 } else if (isActivate()) { 1010 state = State.COMPLETE; 1011 sb.append(context.getText( 1012 com.android.internal.R.string.serviceEnabled)); 1013 // Record CLIR setting 1014 if (sc.equals(SC_CLIR)) { 1015 phone.saveClirSetting(CommandsInterface.CLIR_INVOCATION); 1016 } 1017 } else if (isDeactivate()) { 1018 state = State.COMPLETE; 1019 sb.append(context.getText( 1020 com.android.internal.R.string.serviceDisabled)); 1021 // Record CLIR setting 1022 if (sc.equals(SC_CLIR)) { 1023 phone.saveClirSetting(CommandsInterface.CLIR_SUPPRESSION); 1024 } 1025 } else if (isRegister()) { 1026 state = State.COMPLETE; 1027 sb.append(context.getText( 1028 com.android.internal.R.string.serviceRegistered)); 1029 } else if (isErasure()) { 1030 state = State.COMPLETE; 1031 sb.append(context.getText( 1032 com.android.internal.R.string.serviceErased)); 1033 } else { 1034 state = State.FAILED; 1035 sb.append(context.getText( 1036 com.android.internal.R.string.mmiError)); 1037 } 1038 1039 message = sb; 1040 phone.onMMIDone(this); 1041 } 1042 1043 private void 1044 onGetClirComplete(AsyncResult ar) { 1045 StringBuilder sb = new StringBuilder(getScString()); 1046 sb.append("\n"); 1047 1048 if (ar.exception != null) { 1049 state = State.FAILED; 1050 sb.append(getErrorMessage(ar)); 1051 } else { 1052 int clirArgs[]; 1053 1054 clirArgs = (int[])ar.result; 1055 1056 // the 'm' parameter from TS 27.007 7.7 1057 switch (clirArgs[1]) { 1058 case 0: // CLIR not provisioned 1059 sb.append(context.getText( 1060 com.android.internal.R.string.serviceNotProvisioned)); 1061 state = State.COMPLETE; 1062 break; 1063 1064 case 1: // CLIR provisioned in permanent mode 1065 sb.append(context.getText( 1066 com.android.internal.R.string.CLIRPermanent)); 1067 state = State.COMPLETE; 1068 break; 1069 1070 case 2: // unknown (e.g. no network, etc.) 1071 sb.append(context.getText( 1072 com.android.internal.R.string.mmiError)); 1073 state = State.FAILED; 1074 break; 1075 1076 case 3: // CLIR temporary mode presentation restricted 1077 1078 // the 'n' parameter from TS 27.007 7.7 1079 switch (clirArgs[0]) { 1080 default: 1081 case 0: // Default 1082 sb.append(context.getText( 1083 com.android.internal.R.string.CLIRDefaultOnNextCallOn)); 1084 break; 1085 case 1: // CLIR invocation 1086 sb.append(context.getText( 1087 com.android.internal.R.string.CLIRDefaultOnNextCallOn)); 1088 break; 1089 case 2: // CLIR suppression 1090 sb.append(context.getText( 1091 com.android.internal.R.string.CLIRDefaultOnNextCallOff)); 1092 break; 1093 } 1094 state = State.COMPLETE; 1095 break; 1096 1097 case 4: // CLIR temporary mode presentation allowed 1098 // the 'n' parameter from TS 27.007 7.7 1099 switch (clirArgs[0]) { 1100 default: 1101 case 0: // Default 1102 sb.append(context.getText( 1103 com.android.internal.R.string.CLIRDefaultOffNextCallOff)); 1104 break; 1105 case 1: // CLIR invocation 1106 sb.append(context.getText( 1107 com.android.internal.R.string.CLIRDefaultOffNextCallOn)); 1108 break; 1109 case 2: // CLIR suppression 1110 sb.append(context.getText( 1111 com.android.internal.R.string.CLIRDefaultOffNextCallOff)); 1112 break; 1113 } 1114 1115 state = State.COMPLETE; 1116 break; 1117 } 1118 } 1119 1120 message = sb; 1121 phone.onMMIDone(this); 1122 } 1123 1124 /** 1125 * @param serviceClass 1 bit of the service class bit vectory 1126 * @return String to be used for call forward query MMI response text. 1127 * Returns null if unrecognized 1128 */ 1129 1130 private CharSequence 1131 serviceClassToCFString (int serviceClass) { 1132 switch (serviceClass) { 1133 case SERVICE_CLASS_VOICE: 1134 return context.getText(com.android.internal.R.string.serviceClassVoice); 1135 case SERVICE_CLASS_DATA: 1136 return context.getText(com.android.internal.R.string.serviceClassData); 1137 case SERVICE_CLASS_FAX: 1138 return context.getText(com.android.internal.R.string.serviceClassFAX); 1139 case SERVICE_CLASS_SMS: 1140 return context.getText(com.android.internal.R.string.serviceClassSMS); 1141 case SERVICE_CLASS_DATA_SYNC: 1142 return context.getText(com.android.internal.R.string.serviceClassDataSync); 1143 case SERVICE_CLASS_DATA_ASYNC: 1144 return context.getText(com.android.internal.R.string.serviceClassDataAsync); 1145 case SERVICE_CLASS_PACKET: 1146 return context.getText(com.android.internal.R.string.serviceClassPacket); 1147 case SERVICE_CLASS_PAD: 1148 return context.getText(com.android.internal.R.string.serviceClassPAD); 1149 default: 1150 return null; 1151 } 1152 } 1153 1154 1155 /** one CallForwardInfo + serviceClassMask -> one line of text */ 1156 private CharSequence 1157 makeCFQueryResultMessage(CallForwardInfo info, int serviceClassMask) { 1158 CharSequence template; 1159 String sources[] = {"{0}", "{1}", "{2}"}; 1160 CharSequence destinations[] = new CharSequence[3]; 1161 boolean needTimeTemplate; 1162 1163 // CF_REASON_NO_REPLY also has a time value associated with 1164 // it. All others don't. 1165 1166 needTimeTemplate = 1167 (info.reason == CommandsInterface.CF_REASON_NO_REPLY); 1168 1169 if (info.status == 1) { 1170 if (needTimeTemplate) { 1171 template = context.getText( 1172 com.android.internal.R.string.cfTemplateForwardedTime); 1173 } else { 1174 template = context.getText( 1175 com.android.internal.R.string.cfTemplateForwarded); 1176 } 1177 } else if (info.status == 0 && isEmptyOrNull(info.number)) { 1178 template = context.getText( 1179 com.android.internal.R.string.cfTemplateNotForwarded); 1180 } else { /* (info.status == 0) && !isEmptyOrNull(info.number) */ 1181 // A call forward record that is not active but contains 1182 // a phone number is considered "registered" 1183 1184 if (needTimeTemplate) { 1185 template = context.getText( 1186 com.android.internal.R.string.cfTemplateRegisteredTime); 1187 } else { 1188 template = context.getText( 1189 com.android.internal.R.string.cfTemplateRegistered); 1190 } 1191 } 1192 1193 // In the template (from strings.xmls) 1194 // {0} is one of "bearerServiceCode*" 1195 // {1} is dialing number 1196 // {2} is time in seconds 1197 1198 destinations[0] = serviceClassToCFString(info.serviceClass & serviceClassMask); 1199 destinations[1] = PhoneNumberUtils.stringFromStringAndTOA(info.number, info.toa); 1200 destinations[2] = Integer.toString(info.timeSeconds); 1201 1202 if (info.reason == CommandsInterface.CF_REASON_UNCONDITIONAL && 1203 (info.serviceClass & serviceClassMask) 1204 == CommandsInterface.SERVICE_CLASS_VOICE) { 1205 boolean cffEnabled = (info.status == 1); 1206 phone.mIccRecords.setVoiceCallForwardingFlag(1, cffEnabled); 1207 } 1208 1209 return TextUtils.replace(template, sources, destinations); 1210 } 1211 1212 1213 private void 1214 onQueryCfComplete(AsyncResult ar) { 1215 StringBuilder sb = new StringBuilder(getScString()); 1216 sb.append("\n"); 1217 1218 if (ar.exception != null) { 1219 state = State.FAILED; 1220 sb.append(getErrorMessage(ar)); 1221 } else { 1222 CallForwardInfo infos[]; 1223 1224 infos = (CallForwardInfo[]) ar.result; 1225 1226 if (infos.length == 0) { 1227 // Assume the default is not active 1228 sb.append(context.getText(com.android.internal.R.string.serviceDisabled)); 1229 1230 // Set unconditional CFF in SIM to false 1231 phone.mIccRecords.setVoiceCallForwardingFlag(1, false); 1232 } else { 1233 1234 SpannableStringBuilder tb = new SpannableStringBuilder(); 1235 1236 // Each bit in the service class gets its own result line 1237 // The service classes may be split up over multiple 1238 // CallForwardInfos. So, for each service class, find out 1239 // which CallForwardInfo represents it and then build 1240 // the response text based on that 1241 1242 for (int serviceClassMask = 1 1243 ; serviceClassMask <= SERVICE_CLASS_MAX 1244 ; serviceClassMask <<= 1 1245 ) { 1246 for (int i = 0, s = infos.length; i < s ; i++) { 1247 if ((serviceClassMask & infos[i].serviceClass) != 0) { 1248 tb.append(makeCFQueryResultMessage(infos[i], 1249 serviceClassMask)); 1250 tb.append("\n"); 1251 } 1252 } 1253 } 1254 sb.append(tb); 1255 } 1256 1257 state = State.COMPLETE; 1258 } 1259 1260 message = sb; 1261 phone.onMMIDone(this); 1262 1263 } 1264 1265 private void 1266 onQueryComplete(AsyncResult ar) { 1267 StringBuilder sb = new StringBuilder(getScString()); 1268 sb.append("\n"); 1269 1270 if (ar.exception != null) { 1271 state = State.FAILED; 1272 sb.append(getErrorMessage(ar)); 1273 } else { 1274 int[] ints = (int[])ar.result; 1275 1276 if (ints.length != 0) { 1277 if (ints[0] == 0) { 1278 sb.append(context.getText(com.android.internal.R.string.serviceDisabled)); 1279 } else if (sc.equals(SC_WAIT)) { 1280 // Call Waiting includes additional data in the response. 1281 sb.append(createQueryCallWaitingResultMessage(ints[1])); 1282 } else if (isServiceCodeCallBarring(sc)) { 1283 // ints[0] for Call Barring is a bit vector of services 1284 sb.append(createQueryCallBarringResultMessage(ints[0])); 1285 } else if (ints[0] == 1) { 1286 // for all other services, treat it as a boolean 1287 sb.append(context.getText(com.android.internal.R.string.serviceEnabled)); 1288 } else { 1289 sb.append(context.getText(com.android.internal.R.string.mmiError)); 1290 } 1291 } else { 1292 sb.append(context.getText(com.android.internal.R.string.mmiError)); 1293 } 1294 state = State.COMPLETE; 1295 } 1296 1297 message = sb; 1298 phone.onMMIDone(this); 1299 } 1300 1301 private CharSequence 1302 createQueryCallWaitingResultMessage(int serviceClass) { 1303 StringBuilder sb = 1304 new StringBuilder(context.getText(com.android.internal.R.string.serviceEnabledFor)); 1305 1306 for (int classMask = 1 1307 ; classMask <= SERVICE_CLASS_MAX 1308 ; classMask <<= 1 1309 ) { 1310 if ((classMask & serviceClass) != 0) { 1311 sb.append("\n"); 1312 sb.append(serviceClassToCFString(classMask & serviceClass)); 1313 } 1314 } 1315 return sb; 1316 } 1317 private CharSequence 1318 createQueryCallBarringResultMessage(int serviceClass) 1319 { 1320 StringBuilder sb = 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 1334 /*** 1335 * TODO: It would be nice to have a method here that can take in a dialstring and 1336 * figure out if there is an MMI code embedded within it. This code would replace 1337 * some of the string parsing functionality in the Phone App's 1338 * SpecialCharSequenceMgr class. 1339 */ 1340 1341 @Override 1342 public String toString() { 1343 StringBuilder sb = new StringBuilder("GsmMmiCode {"); 1344 1345 sb.append("State=" + getState()); 1346 if (action != null) sb.append(" action=" + action); 1347 if (sc != null) sb.append(" sc=" + sc); 1348 if (sia != null) sb.append(" sia=" + sia); 1349 if (sib != null) sb.append(" sib=" + sib); 1350 if (sic != null) sb.append(" sic=" + sic); 1351 if (poundString != null) sb.append(" poundString=" + poundString); 1352 if (dialingNumber != null) sb.append(" dialingNumber=" + dialingNumber); 1353 if (pwd != null) sb.append(" pwd=" + pwd); 1354 sb.append("}"); 1355 return sb.toString(); 1356 } 1357} 1358