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