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