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 17 package com.android.internal.telephony.gsm; 18 19 import android.content.Context; 20 import com.android.internal.telephony.*; 21 import com.android.internal.telephony.uicc.IccRecords; 22 import com.android.internal.telephony.uicc.UiccCardApplication; 23 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppState; 24 25 import android.os.*; 26 import android.telephony.PhoneNumberUtils; 27 import android.text.SpannableStringBuilder; 28 import android.text.TextUtils; 29 import android.telephony.Rlog; 30 31 import static com.android.internal.telephony.CommandsInterface.*; 32 33 import java.util.regex.Pattern; 34 import 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 */ 45 public final class GsmMmiCode extends Handler implements MmiCode { 46 static final String LOG_TAG = "GsmMmiCode"; 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 mPhone; 115 Context mContext; 116 UiccCardApplication mUiccApplication; 117 IccRecords mIccRecords; 118 119 String mAction; // One of ACTION_* 120 String mSc; // Service Code 121 String mSia, mSib, mSic; // Service Info a,b,c 122 String mPoundString; // Entire MMI string up to and including # 123 String mDialingNumber; 124 String mPwd; // For password registration 125 126 /** Set to true in processCode, not at newFromDialString time */ 127 private boolean mIsPendingUSSD; 128 129 private boolean mIsUssdRequest; 130 131 private boolean mIsCallFwdReg; 132 State mState = State.PENDING; 133 CharSequence mMessage; 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.mPoundString = makeEmptyNull(m.group(MATCH_GROUP_POUND_STRING)); 192 ret.mAction = makeEmptyNull(m.group(MATCH_GROUP_ACTION)); 193 ret.mSc = makeEmptyNull(m.group(MATCH_GROUP_SERVICE_CODE)); 194 ret.mSia = makeEmptyNull(m.group(MATCH_GROUP_SIA)); 195 ret.mSib = makeEmptyNull(m.group(MATCH_GROUP_SIB)); 196 ret.mSic = makeEmptyNull(m.group(MATCH_GROUP_SIC)); 197 ret.mPwd = makeEmptyNull(m.group(MATCH_GROUP_PWD_CONFIRM)); 198 ret.mDialingNumber = 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.mDialingNumber != null && 205 ret.mDialingNumber.endsWith("#") && 206 dialString.endsWith("#")){ 207 ret = new GsmMmiCode(phone, app); 208 ret.mPoundString = 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.mPoundString = 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.mDialingNumber = 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.mMessage = ussdMessage; 237 ret.mIsUssdRequest = isUssdRequest; 238 239 // If it's a request, set to PENDING so that it's cancelable. 240 if (isUssdRequest) { 241 ret.mIsPendingUSSD = true; 242 ret.mState = State.PENDING; 243 } else { 244 ret.mState = 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.mMessage = ussdMessge; 256 ret.mState = State.PENDING; 257 ret.mIsPendingUSSD = 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 mPhone = phone; 409 mContext = phone.getContext(); 410 mUiccApplication = app; 411 if (app != null) { 412 mIccRecords = app.getIccRecords(); 413 } 414 } 415 416 //***** MmiCode implementation 417 418 @Override 419 public State 420 getState() { 421 return mState; 422 } 423 424 @Override 425 public CharSequence 426 getMessage() { 427 return mMessage; 428 } 429 430 // inherited javadoc suffices 431 @Override 432 public void 433 cancel() { 434 // Complete or failed cannot be cancelled 435 if (mState == State.COMPLETE || mState == State.FAILED) { 436 return; 437 } 438 439 mState = State.CANCELLED; 440 441 if (mIsPendingUSSD) { 442 /* 443 * There can only be one pending USSD session, so tell the radio to 444 * cancel it. 445 */ 446 mPhone.mCi.cancelPendingUssd(obtainMessage(EVENT_USSD_CANCEL_COMPLETE, this)); 447 448 /* 449 * Don't call phone.onMMIDone here; wait for CANCEL_COMPLETE notice 450 * from RIL. 451 */ 452 } else { 453 // TODO in cases other than USSD, it would be nice to cancel 454 // the pending radio operation. This requires RIL cancellation 455 // support, which does not presently exist. 456 457 mPhone.onMMIDone (this); 458 } 459 460 } 461 462 @Override 463 public boolean isCancelable() { 464 /* Can only cancel pending USSD sessions. */ 465 return mIsPendingUSSD; 466 } 467 468 //***** Instance Methods 469 470 /** Does this dial string contain a structured or unstructured MMI code? */ 471 boolean 472 isMMI() { 473 return mPoundString != null; 474 } 475 476 /* Is this a 1 or 2 digit "short code" as defined in TS 22.030 sec 6.5.3.2? */ 477 boolean 478 isShortCode() { 479 return mPoundString == null 480 && mDialingNumber != null && mDialingNumber.length() <= 2; 481 482 } 483 484 static private boolean 485 isTwoDigitShortCode(Context context, String dialString) { 486 Rlog.d(LOG_TAG, "isTwoDigitShortCode"); 487 488 if (dialString == null || dialString.length() != 2) return false; 489 490 if (sTwoDigitNumberPattern == null) { 491 sTwoDigitNumberPattern = context.getResources().getStringArray( 492 com.android.internal.R.array.config_twoDigitNumberPattern); 493 } 494 495 for (String dialnumber : sTwoDigitNumberPattern) { 496 Rlog.d(LOG_TAG, "Two Digit Number Pattern " + dialnumber); 497 if (dialString.equals(dialnumber)) { 498 Rlog.d(LOG_TAG, "Two Digit Number Pattern -true"); 499 return true; 500 } 501 } 502 Rlog.d(LOG_TAG, "Two Digit Number Pattern -false"); 503 return false; 504 } 505 506 /** 507 * Helper function for newFromDialString. Returns true if dialString appears 508 * to be a short code AND conditions are correct for it to be treated as 509 * such. 510 */ 511 static private boolean isShortCode(String dialString, GSMPhone phone) { 512 // Refer to TS 22.030 Figure 3.5.3.2: 513 if (dialString == null) { 514 return false; 515 } 516 517 // Illegal dial string characters will give a ZERO length. 518 // At this point we do not want to crash as any application with 519 // call privileges may send a non dial string. 520 // It return false as when the dialString is equal to NULL. 521 if (dialString.length() == 0) { 522 return false; 523 } 524 525 if (PhoneNumberUtils.isLocalEmergencyNumber(dialString, phone.getContext())) { 526 return false; 527 } else { 528 return isShortCodeUSSD(dialString, phone); 529 } 530 } 531 532 /** 533 * Helper function for isShortCode. Returns true if dialString appears to be 534 * a short code and it is a USSD structure 535 * 536 * According to the 3PGG TS 22.030 specification Figure 3.5.3.2: A 1 or 2 537 * digit "short code" is treated as USSD if it is entered while on a call or 538 * does not satisfy the condition (exactly 2 digits && starts with '1'), there 539 * are however exceptions to this rule (see below) 540 * 541 * Exception (1) to Call initiation is: If the user of the device is already in a call 542 * and enters a Short String without any #-key at the end and the length of the Short String is 543 * equal or less then the MAX_LENGTH_SHORT_CODE [constant that is equal to 2] 544 * 545 * The phone shall initiate a USSD/SS commands. 546 */ 547 static private boolean isShortCodeUSSD(String dialString, GSMPhone phone) { 548 if (dialString != null && dialString.length() <= MAX_LENGTH_SHORT_CODE) { 549 if (phone.isInCall()) { 550 return true; 551 } 552 553 if (dialString.length() != MAX_LENGTH_SHORT_CODE || 554 dialString.charAt(0) != '1') { 555 return true; 556 } 557 } 558 return false; 559 } 560 561 /** 562 * @return true if the Service Code is PIN/PIN2/PUK/PUK2-related 563 */ 564 boolean isPinCommand() { 565 return mSc != null && (mSc.equals(SC_PIN) || mSc.equals(SC_PIN2) 566 || mSc.equals(SC_PUK) || mSc.equals(SC_PUK2)); 567 } 568 569 /** 570 * See TS 22.030 Annex B. 571 * In temporary mode, to suppress CLIR for a single call, enter: 572 * " * 31 # [called number] SEND " 573 * In temporary mode, to invoke CLIR for a single call enter: 574 * " # 31 # [called number] SEND " 575 */ 576 boolean 577 isTemporaryModeCLIR() { 578 return mSc != null && mSc.equals(SC_CLIR) && mDialingNumber != null 579 && (isActivate() || isDeactivate()); 580 } 581 582 /** 583 * returns CommandsInterface.CLIR_* 584 * See also isTemporaryModeCLIR() 585 */ 586 int 587 getCLIRMode() { 588 if (mSc != null && mSc.equals(SC_CLIR)) { 589 if (isActivate()) { 590 return CommandsInterface.CLIR_SUPPRESSION; 591 } else if (isDeactivate()) { 592 return CommandsInterface.CLIR_INVOCATION; 593 } 594 } 595 596 return CommandsInterface.CLIR_DEFAULT; 597 } 598 599 boolean isActivate() { 600 return mAction != null && mAction.equals(ACTION_ACTIVATE); 601 } 602 603 boolean isDeactivate() { 604 return mAction != null && mAction.equals(ACTION_DEACTIVATE); 605 } 606 607 boolean isInterrogate() { 608 return mAction != null && mAction.equals(ACTION_INTERROGATE); 609 } 610 611 boolean isRegister() { 612 return mAction != null && mAction.equals(ACTION_REGISTER); 613 } 614 615 boolean isErasure() { 616 return mAction != null && mAction.equals(ACTION_ERASURE); 617 } 618 619 /** 620 * Returns true if this is a USSD code that's been submitted to the 621 * network...eg, after processCode() is called 622 */ 623 public boolean isPendingUSSD() { 624 return mIsPendingUSSD; 625 } 626 627 @Override 628 public boolean isUssdRequest() { 629 return mIsUssdRequest; 630 } 631 632 /** Process a MMI code or short code...anything that isn't a dialing number */ 633 void 634 processCode () { 635 try { 636 if (isShortCode()) { 637 Rlog.d(LOG_TAG, "isShortCode"); 638 // These just get treated as USSD. 639 sendUssd(mDialingNumber); 640 } else if (mDialingNumber != null) { 641 // We should have no dialing numbers here 642 throw new RuntimeException ("Invalid or Unsupported MMI Code"); 643 } else if (mSc != null && mSc.equals(SC_CLIP)) { 644 Rlog.d(LOG_TAG, "is CLIP"); 645 if (isInterrogate()) { 646 mPhone.mCi.queryCLIP( 647 obtainMessage(EVENT_QUERY_COMPLETE, this)); 648 } else { 649 throw new RuntimeException ("Invalid or Unsupported MMI Code"); 650 } 651 } else if (mSc != null && mSc.equals(SC_CLIR)) { 652 Rlog.d(LOG_TAG, "is CLIR"); 653 if (isActivate()) { 654 mPhone.mCi.setCLIR(CommandsInterface.CLIR_INVOCATION, 655 obtainMessage(EVENT_SET_COMPLETE, this)); 656 } else if (isDeactivate()) { 657 mPhone.mCi.setCLIR(CommandsInterface.CLIR_SUPPRESSION, 658 obtainMessage(EVENT_SET_COMPLETE, this)); 659 } else if (isInterrogate()) { 660 mPhone.mCi.getCLIR( 661 obtainMessage(EVENT_GET_CLIR_COMPLETE, this)); 662 } else { 663 throw new RuntimeException ("Invalid or Unsupported MMI Code"); 664 } 665 } else if (isServiceCodeCallForwarding(mSc)) { 666 Rlog.d(LOG_TAG, "is CF"); 667 668 String dialingNumber = mSia; 669 int serviceClass = siToServiceClass(mSib); 670 int reason = scToCallForwardReason(mSc); 671 int time = siToTime(mSic); 672 673 if (isInterrogate()) { 674 mPhone.mCi.queryCallForwardStatus( 675 reason, serviceClass, dialingNumber, 676 obtainMessage(EVENT_QUERY_CF_COMPLETE, this)); 677 } else { 678 int cfAction; 679 680 if (isActivate()) { 681 // 3GPP TS 22.030 6.5.2 682 // a call forwarding request with a single * would be 683 // interpreted as registration if containing a forwarded-to 684 // number, or an activation if not 685 if (isEmptyOrNull(dialingNumber)) { 686 cfAction = CommandsInterface.CF_ACTION_ENABLE; 687 mIsCallFwdReg = false; 688 } else { 689 cfAction = CommandsInterface.CF_ACTION_REGISTRATION; 690 mIsCallFwdReg = true; 691 } 692 } else if (isDeactivate()) { 693 cfAction = CommandsInterface.CF_ACTION_DISABLE; 694 } else if (isRegister()) { 695 cfAction = CommandsInterface.CF_ACTION_REGISTRATION; 696 } else if (isErasure()) { 697 cfAction = CommandsInterface.CF_ACTION_ERASURE; 698 } else { 699 throw new RuntimeException ("invalid action"); 700 } 701 702 int isSettingUnconditionalVoice = 703 (((reason == CommandsInterface.CF_REASON_UNCONDITIONAL) || 704 (reason == CommandsInterface.CF_REASON_ALL)) && 705 (((serviceClass & CommandsInterface.SERVICE_CLASS_VOICE) != 0) || 706 (serviceClass == CommandsInterface.SERVICE_CLASS_NONE))) ? 1 : 0; 707 708 int isEnableDesired = 709 ((cfAction == CommandsInterface.CF_ACTION_ENABLE) || 710 (cfAction == CommandsInterface.CF_ACTION_REGISTRATION)) ? 1 : 0; 711 712 Rlog.d(LOG_TAG, "is CF setCallForward"); 713 mPhone.mCi.setCallForward(cfAction, reason, serviceClass, 714 dialingNumber, time, obtainMessage( 715 EVENT_SET_CFF_COMPLETE, 716 isSettingUnconditionalVoice, 717 isEnableDesired, this)); 718 } 719 } else if (isServiceCodeCallBarring(mSc)) { 720 // sia = password 721 // sib = basic service group 722 723 String password = mSia; 724 int serviceClass = siToServiceClass(mSib); 725 String facility = scToBarringFacility(mSc); 726 727 if (isInterrogate()) { 728 mPhone.mCi.queryFacilityLock(facility, password, 729 serviceClass, obtainMessage(EVENT_QUERY_COMPLETE, this)); 730 } else if (isActivate() || isDeactivate()) { 731 mPhone.mCi.setFacilityLock(facility, isActivate(), password, 732 serviceClass, obtainMessage(EVENT_SET_COMPLETE, this)); 733 } else { 734 throw new RuntimeException ("Invalid or Unsupported MMI Code"); 735 } 736 737 } else if (mSc != null && mSc.equals(SC_PWD)) { 738 // sia = fac 739 // sib = old pwd 740 // sic = new pwd 741 // pwd = new pwd 742 String facility; 743 String oldPwd = mSib; 744 String newPwd = mSic; 745 if (isActivate() || isRegister()) { 746 // Even though ACTIVATE is acceptable, this is really termed a REGISTER 747 mAction = ACTION_REGISTER; 748 749 if (mSia == null) { 750 // If sc was not specified, treat it as BA_ALL. 751 facility = CommandsInterface.CB_FACILITY_BA_ALL; 752 } else { 753 facility = scToBarringFacility(mSia); 754 } 755 if (newPwd.equals(mPwd)) { 756 mPhone.mCi.changeBarringPassword(facility, oldPwd, 757 newPwd, obtainMessage(EVENT_SET_COMPLETE, this)); 758 } else { 759 // password mismatch; return error 760 handlePasswordError(com.android.internal.R.string.passwordIncorrect); 761 } 762 } else { 763 throw new RuntimeException ("Invalid or Unsupported MMI Code"); 764 } 765 766 } else if (mSc != null && mSc.equals(SC_WAIT)) { 767 // sia = basic service group 768 int serviceClass = siToServiceClass(mSia); 769 770 if (isActivate() || isDeactivate()) { 771 mPhone.mCi.setCallWaiting(isActivate(), serviceClass, 772 obtainMessage(EVENT_SET_COMPLETE, this)); 773 } else if (isInterrogate()) { 774 mPhone.mCi.queryCallWaiting(serviceClass, 775 obtainMessage(EVENT_QUERY_COMPLETE, this)); 776 } else { 777 throw new RuntimeException ("Invalid or Unsupported MMI Code"); 778 } 779 } else if (isPinCommand()) { 780 // sia = old PIN or PUK 781 // sib = new PIN 782 // sic = new PIN 783 String oldPinOrPuk = mSia; 784 String newPin = mSib; 785 int pinLen = newPin.length(); 786 if (isRegister()) { 787 if (!newPin.equals(mSic)) { 788 // password mismatch; return error 789 handlePasswordError(com.android.internal.R.string.mismatchPin); 790 } else if (pinLen < 4 || pinLen > 8 ) { 791 // invalid length 792 handlePasswordError(com.android.internal.R.string.invalidPin); 793 } else if (mSc.equals(SC_PIN) && 794 mUiccApplication != null && 795 mUiccApplication.getState() == AppState.APPSTATE_PUK ) { 796 // Sim is puk-locked 797 handlePasswordError(com.android.internal.R.string.needPuk); 798 } else { 799 // pre-checks OK 800 if (mSc.equals(SC_PIN)) { 801 mPhone.mCi.changeIccPin(oldPinOrPuk, newPin, 802 obtainMessage(EVENT_SET_COMPLETE, this)); 803 } else if (mSc.equals(SC_PIN2)) { 804 mPhone.mCi.changeIccPin2(oldPinOrPuk, newPin, 805 obtainMessage(EVENT_SET_COMPLETE, this)); 806 } else if (mSc.equals(SC_PUK)) { 807 mPhone.mCi.supplyIccPuk(oldPinOrPuk, newPin, 808 obtainMessage(EVENT_SET_COMPLETE, this)); 809 } else if (mSc.equals(SC_PUK2)) { 810 mPhone.mCi.supplyIccPuk2(oldPinOrPuk, newPin, 811 obtainMessage(EVENT_SET_COMPLETE, this)); 812 } 813 } 814 } else { 815 throw new RuntimeException ("Invalid or Unsupported MMI Code"); 816 } 817 } else if (mPoundString != null) { 818 sendUssd(mPoundString); 819 } else { 820 throw new RuntimeException ("Invalid or Unsupported MMI Code"); 821 } 822 } catch (RuntimeException exc) { 823 mState = State.FAILED; 824 mMessage = mContext.getText(com.android.internal.R.string.mmiError); 825 mPhone.onMMIDone(this); 826 } 827 } 828 829 private void handlePasswordError(int res) { 830 mState = State.FAILED; 831 StringBuilder sb = new StringBuilder(getScString()); 832 sb.append("\n"); 833 sb.append(mContext.getText(res)); 834 mMessage = sb; 835 mPhone.onMMIDone(this); 836 } 837 838 /** 839 * Called from GSMPhone 840 * 841 * An unsolicited USSD NOTIFY or REQUEST has come in matching 842 * up with this pending USSD request 843 * 844 * Note: If REQUEST, this exchange is complete, but the session remains 845 * active (ie, the network expects user input). 846 */ 847 void 848 onUssdFinished(String ussdMessage, boolean isUssdRequest) { 849 if (mState == State.PENDING) { 850 if (ussdMessage == null) { 851 mMessage = mContext.getText(com.android.internal.R.string.mmiComplete); 852 } else { 853 mMessage = ussdMessage; 854 } 855 mIsUssdRequest = isUssdRequest; 856 // If it's a request, leave it PENDING so that it's cancelable. 857 if (!isUssdRequest) { 858 mState = State.COMPLETE; 859 } 860 861 mPhone.onMMIDone(this); 862 } 863 } 864 865 /** 866 * Called from GSMPhone 867 * 868 * The radio has reset, and this is still pending 869 */ 870 871 void 872 onUssdFinishedError() { 873 if (mState == State.PENDING) { 874 mState = State.FAILED; 875 mMessage = mContext.getText(com.android.internal.R.string.mmiError); 876 877 mPhone.onMMIDone(this); 878 } 879 } 880 881 void sendUssd(String ussdMessage) { 882 // Treat this as a USSD string 883 mIsPendingUSSD = true; 884 885 // Note that unlike most everything else, the USSD complete 886 // response does not complete this MMI code...we wait for 887 // an unsolicited USSD "Notify" or "Request". 888 // The matching up of this is done in GSMPhone. 889 890 mPhone.mCi.sendUSSD(ussdMessage, 891 obtainMessage(EVENT_USSD_COMPLETE, this)); 892 } 893 894 /** Called from GSMPhone.handleMessage; not a Handler subclass */ 895 @Override 896 public void 897 handleMessage (Message msg) { 898 AsyncResult ar; 899 900 switch (msg.what) { 901 case EVENT_SET_COMPLETE: 902 ar = (AsyncResult) (msg.obj); 903 904 onSetComplete(ar); 905 break; 906 907 case EVENT_SET_CFF_COMPLETE: 908 ar = (AsyncResult) (msg.obj); 909 910 /* 911 * msg.arg1 = 1 means to set unconditional voice call forwarding 912 * msg.arg2 = 1 means to enable voice call forwarding 913 */ 914 if ((ar.exception == null) && (msg.arg1 == 1)) { 915 boolean cffEnabled = (msg.arg2 == 1); 916 if (mIccRecords != null) { 917 mIccRecords.setVoiceCallForwardingFlag(1, cffEnabled); 918 } 919 } 920 921 onSetComplete(ar); 922 break; 923 924 case EVENT_GET_CLIR_COMPLETE: 925 ar = (AsyncResult) (msg.obj); 926 onGetClirComplete(ar); 927 break; 928 929 case EVENT_QUERY_CF_COMPLETE: 930 ar = (AsyncResult) (msg.obj); 931 onQueryCfComplete(ar); 932 break; 933 934 case EVENT_QUERY_COMPLETE: 935 ar = (AsyncResult) (msg.obj); 936 onQueryComplete(ar); 937 break; 938 939 case EVENT_USSD_COMPLETE: 940 ar = (AsyncResult) (msg.obj); 941 942 if (ar.exception != null) { 943 mState = State.FAILED; 944 mMessage = getErrorMessage(ar); 945 946 mPhone.onMMIDone(this); 947 } 948 949 // Note that unlike most everything else, the USSD complete 950 // response does not complete this MMI code...we wait for 951 // an unsolicited USSD "Notify" or "Request". 952 // The matching up of this is done in GSMPhone. 953 954 break; 955 956 case EVENT_USSD_CANCEL_COMPLETE: 957 mPhone.onMMIDone(this); 958 break; 959 } 960 } 961 //***** Private instance methods 962 963 private CharSequence getErrorMessage(AsyncResult ar) { 964 965 if (ar.exception instanceof CommandException) { 966 CommandException.Error err = ((CommandException)(ar.exception)).getCommandError(); 967 if (err == CommandException.Error.FDN_CHECK_FAILURE) { 968 Rlog.i(LOG_TAG, "FDN_CHECK_FAILURE"); 969 return mContext.getText(com.android.internal.R.string.mmiFdnError); 970 } 971 } 972 973 return mContext.getText(com.android.internal.R.string.mmiError); 974 } 975 976 private CharSequence getScString() { 977 if (mSc != null) { 978 if (isServiceCodeCallBarring(mSc)) { 979 return mContext.getText(com.android.internal.R.string.BaMmi); 980 } else if (isServiceCodeCallForwarding(mSc)) { 981 return mContext.getText(com.android.internal.R.string.CfMmi); 982 } else if (mSc.equals(SC_CLIP)) { 983 return mContext.getText(com.android.internal.R.string.ClipMmi); 984 } else if (mSc.equals(SC_CLIR)) { 985 return mContext.getText(com.android.internal.R.string.ClirMmi); 986 } else if (mSc.equals(SC_PWD)) { 987 return mContext.getText(com.android.internal.R.string.PwdMmi); 988 } else if (mSc.equals(SC_WAIT)) { 989 return mContext.getText(com.android.internal.R.string.CwMmi); 990 } else if (isPinCommand()) { 991 return mContext.getText(com.android.internal.R.string.PinMmi); 992 } 993 } 994 995 return ""; 996 } 997 998 private void 999 onSetComplete(AsyncResult ar){ 1000 StringBuilder sb = new StringBuilder(getScString()); 1001 sb.append("\n"); 1002 1003 if (ar.exception != null) { 1004 mState = State.FAILED; 1005 if (ar.exception instanceof CommandException) { 1006 CommandException.Error err = ((CommandException)(ar.exception)).getCommandError(); 1007 if (err == CommandException.Error.PASSWORD_INCORRECT) { 1008 if (isPinCommand()) { 1009 // look specifically for the PUK commands and adjust 1010 // the message accordingly. 1011 if (mSc.equals(SC_PUK) || mSc.equals(SC_PUK2)) { 1012 sb.append(mContext.getText( 1013 com.android.internal.R.string.badPuk)); 1014 } else { 1015 sb.append(mContext.getText( 1016 com.android.internal.R.string.badPin)); 1017 } 1018 } else { 1019 sb.append(mContext.getText( 1020 com.android.internal.R.string.passwordIncorrect)); 1021 } 1022 } else if (err == CommandException.Error.SIM_PUK2) { 1023 sb.append(mContext.getText( 1024 com.android.internal.R.string.badPin)); 1025 sb.append("\n"); 1026 sb.append(mContext.getText( 1027 com.android.internal.R.string.needPuk2)); 1028 } else if (err == CommandException.Error.FDN_CHECK_FAILURE) { 1029 Rlog.i(LOG_TAG, "FDN_CHECK_FAILURE"); 1030 sb.append(mContext.getText(com.android.internal.R.string.mmiFdnError)); 1031 } else { 1032 sb.append(mContext.getText( 1033 com.android.internal.R.string.mmiError)); 1034 } 1035 } else { 1036 sb.append(mContext.getText( 1037 com.android.internal.R.string.mmiError)); 1038 } 1039 } else if (isActivate()) { 1040 mState = State.COMPLETE; 1041 if (mIsCallFwdReg) { 1042 sb.append(mContext.getText( 1043 com.android.internal.R.string.serviceRegistered)); 1044 } else { 1045 sb.append(mContext.getText( 1046 com.android.internal.R.string.serviceEnabled)); 1047 } 1048 // Record CLIR setting 1049 if (mSc.equals(SC_CLIR)) { 1050 mPhone.saveClirSetting(CommandsInterface.CLIR_INVOCATION); 1051 } 1052 } else if (isDeactivate()) { 1053 mState = State.COMPLETE; 1054 sb.append(mContext.getText( 1055 com.android.internal.R.string.serviceDisabled)); 1056 // Record CLIR setting 1057 if (mSc.equals(SC_CLIR)) { 1058 mPhone.saveClirSetting(CommandsInterface.CLIR_SUPPRESSION); 1059 } 1060 } else if (isRegister()) { 1061 mState = State.COMPLETE; 1062 sb.append(mContext.getText( 1063 com.android.internal.R.string.serviceRegistered)); 1064 } else if (isErasure()) { 1065 mState = State.COMPLETE; 1066 sb.append(mContext.getText( 1067 com.android.internal.R.string.serviceErased)); 1068 } else { 1069 mState = State.FAILED; 1070 sb.append(mContext.getText( 1071 com.android.internal.R.string.mmiError)); 1072 } 1073 1074 mMessage = sb; 1075 mPhone.onMMIDone(this); 1076 } 1077 1078 private void 1079 onGetClirComplete(AsyncResult ar) { 1080 StringBuilder sb = new StringBuilder(getScString()); 1081 sb.append("\n"); 1082 1083 if (ar.exception != null) { 1084 mState = State.FAILED; 1085 sb.append(getErrorMessage(ar)); 1086 } else { 1087 int clirArgs[]; 1088 1089 clirArgs = (int[])ar.result; 1090 1091 // the 'm' parameter from TS 27.007 7.7 1092 switch (clirArgs[1]) { 1093 case 0: // CLIR not provisioned 1094 sb.append(mContext.getText( 1095 com.android.internal.R.string.serviceNotProvisioned)); 1096 mState = State.COMPLETE; 1097 break; 1098 1099 case 1: // CLIR provisioned in permanent mode 1100 sb.append(mContext.getText( 1101 com.android.internal.R.string.CLIRPermanent)); 1102 mState = State.COMPLETE; 1103 break; 1104 1105 case 2: // unknown (e.g. no network, etc.) 1106 sb.append(mContext.getText( 1107 com.android.internal.R.string.mmiError)); 1108 mState = State.FAILED; 1109 break; 1110 1111 case 3: // CLIR temporary mode presentation restricted 1112 1113 // the 'n' parameter from TS 27.007 7.7 1114 switch (clirArgs[0]) { 1115 default: 1116 case 0: // Default 1117 sb.append(mContext.getText( 1118 com.android.internal.R.string.CLIRDefaultOnNextCallOn)); 1119 break; 1120 case 1: // CLIR invocation 1121 sb.append(mContext.getText( 1122 com.android.internal.R.string.CLIRDefaultOnNextCallOn)); 1123 break; 1124 case 2: // CLIR suppression 1125 sb.append(mContext.getText( 1126 com.android.internal.R.string.CLIRDefaultOnNextCallOff)); 1127 break; 1128 } 1129 mState = State.COMPLETE; 1130 break; 1131 1132 case 4: // CLIR temporary mode presentation allowed 1133 // the 'n' parameter from TS 27.007 7.7 1134 switch (clirArgs[0]) { 1135 default: 1136 case 0: // Default 1137 sb.append(mContext.getText( 1138 com.android.internal.R.string.CLIRDefaultOffNextCallOff)); 1139 break; 1140 case 1: // CLIR invocation 1141 sb.append(mContext.getText( 1142 com.android.internal.R.string.CLIRDefaultOffNextCallOn)); 1143 break; 1144 case 2: // CLIR suppression 1145 sb.append(mContext.getText( 1146 com.android.internal.R.string.CLIRDefaultOffNextCallOff)); 1147 break; 1148 } 1149 1150 mState = State.COMPLETE; 1151 break; 1152 } 1153 } 1154 1155 mMessage = sb; 1156 mPhone.onMMIDone(this); 1157 } 1158 1159 /** 1160 * @param serviceClass 1 bit of the service class bit vectory 1161 * @return String to be used for call forward query MMI response text. 1162 * Returns null if unrecognized 1163 */ 1164 1165 private CharSequence 1166 serviceClassToCFString (int serviceClass) { 1167 switch (serviceClass) { 1168 case SERVICE_CLASS_VOICE: 1169 return mContext.getText(com.android.internal.R.string.serviceClassVoice); 1170 case SERVICE_CLASS_DATA: 1171 return mContext.getText(com.android.internal.R.string.serviceClassData); 1172 case SERVICE_CLASS_FAX: 1173 return mContext.getText(com.android.internal.R.string.serviceClassFAX); 1174 case SERVICE_CLASS_SMS: 1175 return mContext.getText(com.android.internal.R.string.serviceClassSMS); 1176 case SERVICE_CLASS_DATA_SYNC: 1177 return mContext.getText(com.android.internal.R.string.serviceClassDataSync); 1178 case SERVICE_CLASS_DATA_ASYNC: 1179 return mContext.getText(com.android.internal.R.string.serviceClassDataAsync); 1180 case SERVICE_CLASS_PACKET: 1181 return mContext.getText(com.android.internal.R.string.serviceClassPacket); 1182 case SERVICE_CLASS_PAD: 1183 return mContext.getText(com.android.internal.R.string.serviceClassPAD); 1184 default: 1185 return null; 1186 } 1187 } 1188 1189 1190 /** one CallForwardInfo + serviceClassMask -> one line of text */ 1191 private CharSequence 1192 makeCFQueryResultMessage(CallForwardInfo info, int serviceClassMask) { 1193 CharSequence template; 1194 String sources[] = {"{0}", "{1}", "{2}"}; 1195 CharSequence destinations[] = new CharSequence[3]; 1196 boolean needTimeTemplate; 1197 1198 // CF_REASON_NO_REPLY also has a time value associated with 1199 // it. All others don't. 1200 1201 needTimeTemplate = 1202 (info.reason == CommandsInterface.CF_REASON_NO_REPLY); 1203 1204 if (info.status == 1) { 1205 if (needTimeTemplate) { 1206 template = mContext.getText( 1207 com.android.internal.R.string.cfTemplateForwardedTime); 1208 } else { 1209 template = mContext.getText( 1210 com.android.internal.R.string.cfTemplateForwarded); 1211 } 1212 } else if (info.status == 0 && isEmptyOrNull(info.number)) { 1213 template = mContext.getText( 1214 com.android.internal.R.string.cfTemplateNotForwarded); 1215 } else { /* (info.status == 0) && !isEmptyOrNull(info.number) */ 1216 // A call forward record that is not active but contains 1217 // a phone number is considered "registered" 1218 1219 if (needTimeTemplate) { 1220 template = mContext.getText( 1221 com.android.internal.R.string.cfTemplateRegisteredTime); 1222 } else { 1223 template = mContext.getText( 1224 com.android.internal.R.string.cfTemplateRegistered); 1225 } 1226 } 1227 1228 // In the template (from strings.xmls) 1229 // {0} is one of "bearerServiceCode*" 1230 // {1} is dialing number 1231 // {2} is time in seconds 1232 1233 destinations[0] = serviceClassToCFString(info.serviceClass & serviceClassMask); 1234 destinations[1] = PhoneNumberUtils.stringFromStringAndTOA(info.number, info.toa); 1235 destinations[2] = Integer.toString(info.timeSeconds); 1236 1237 if (info.reason == CommandsInterface.CF_REASON_UNCONDITIONAL && 1238 (info.serviceClass & serviceClassMask) 1239 == CommandsInterface.SERVICE_CLASS_VOICE) { 1240 boolean cffEnabled = (info.status == 1); 1241 if (mIccRecords != null) { 1242 mIccRecords.setVoiceCallForwardingFlag(1, cffEnabled); 1243 } 1244 } 1245 1246 return TextUtils.replace(template, sources, destinations); 1247 } 1248 1249 1250 private void 1251 onQueryCfComplete(AsyncResult ar) { 1252 StringBuilder sb = new StringBuilder(getScString()); 1253 sb.append("\n"); 1254 1255 if (ar.exception != null) { 1256 mState = State.FAILED; 1257 sb.append(getErrorMessage(ar)); 1258 } else { 1259 CallForwardInfo infos[]; 1260 1261 infos = (CallForwardInfo[]) ar.result; 1262 1263 if (infos.length == 0) { 1264 // Assume the default is not active 1265 sb.append(mContext.getText(com.android.internal.R.string.serviceDisabled)); 1266 1267 // Set unconditional CFF in SIM to false 1268 if (mIccRecords != null) { 1269 mIccRecords.setVoiceCallForwardingFlag(1, false); 1270 } 1271 } else { 1272 1273 SpannableStringBuilder tb = new SpannableStringBuilder(); 1274 1275 // Each bit in the service class gets its own result line 1276 // The service classes may be split up over multiple 1277 // CallForwardInfos. So, for each service class, find out 1278 // which CallForwardInfo represents it and then build 1279 // the response text based on that 1280 1281 for (int serviceClassMask = 1 1282 ; serviceClassMask <= SERVICE_CLASS_MAX 1283 ; serviceClassMask <<= 1 1284 ) { 1285 for (int i = 0, s = infos.length; i < s ; i++) { 1286 if ((serviceClassMask & infos[i].serviceClass) != 0) { 1287 tb.append(makeCFQueryResultMessage(infos[i], 1288 serviceClassMask)); 1289 tb.append("\n"); 1290 } 1291 } 1292 } 1293 sb.append(tb); 1294 } 1295 1296 mState = State.COMPLETE; 1297 } 1298 1299 mMessage = sb; 1300 mPhone.onMMIDone(this); 1301 1302 } 1303 1304 private void 1305 onQueryComplete(AsyncResult ar) { 1306 StringBuilder sb = new StringBuilder(getScString()); 1307 sb.append("\n"); 1308 1309 if (ar.exception != null) { 1310 mState = State.FAILED; 1311 sb.append(getErrorMessage(ar)); 1312 } else { 1313 int[] ints = (int[])ar.result; 1314 1315 if (ints.length != 0) { 1316 if (ints[0] == 0) { 1317 sb.append(mContext.getText(com.android.internal.R.string.serviceDisabled)); 1318 } else if (mSc.equals(SC_WAIT)) { 1319 // Call Waiting includes additional data in the response. 1320 sb.append(createQueryCallWaitingResultMessage(ints[1])); 1321 } else if (isServiceCodeCallBarring(mSc)) { 1322 // ints[0] for Call Barring is a bit vector of services 1323 sb.append(createQueryCallBarringResultMessage(ints[0])); 1324 } else if (ints[0] == 1) { 1325 // for all other services, treat it as a boolean 1326 sb.append(mContext.getText(com.android.internal.R.string.serviceEnabled)); 1327 } else { 1328 sb.append(mContext.getText(com.android.internal.R.string.mmiError)); 1329 } 1330 } else { 1331 sb.append(mContext.getText(com.android.internal.R.string.mmiError)); 1332 } 1333 mState = State.COMPLETE; 1334 } 1335 1336 mMessage = sb; 1337 mPhone.onMMIDone(this); 1338 } 1339 1340 private CharSequence 1341 createQueryCallWaitingResultMessage(int serviceClass) { 1342 StringBuilder sb = 1343 new StringBuilder(mContext.getText(com.android.internal.R.string.serviceEnabledFor)); 1344 1345 for (int classMask = 1 1346 ; classMask <= SERVICE_CLASS_MAX 1347 ; classMask <<= 1 1348 ) { 1349 if ((classMask & serviceClass) != 0) { 1350 sb.append("\n"); 1351 sb.append(serviceClassToCFString(classMask & serviceClass)); 1352 } 1353 } 1354 return sb; 1355 } 1356 private CharSequence 1357 createQueryCallBarringResultMessage(int serviceClass) 1358 { 1359 StringBuilder sb = new StringBuilder(mContext.getText(com.android.internal.R.string.serviceEnabledFor)); 1360 1361 for (int classMask = 1 1362 ; classMask <= SERVICE_CLASS_MAX 1363 ; classMask <<= 1 1364 ) { 1365 if ((classMask & serviceClass) != 0) { 1366 sb.append("\n"); 1367 sb.append(serviceClassToCFString(classMask & serviceClass)); 1368 } 1369 } 1370 return sb; 1371 } 1372 1373 /*** 1374 * TODO: It would be nice to have a method here that can take in a dialstring and 1375 * figure out if there is an MMI code embedded within it. This code would replace 1376 * some of the string parsing functionality in the Phone App's 1377 * SpecialCharSequenceMgr class. 1378 */ 1379 1380 @Override 1381 public String toString() { 1382 StringBuilder sb = new StringBuilder("GsmMmiCode {"); 1383 1384 sb.append("State=" + getState()); 1385 if (mAction != null) sb.append(" action=" + mAction); 1386 if (mSc != null) sb.append(" sc=" + mSc); 1387 if (mSia != null) sb.append(" sia=" + mSia); 1388 if (mSib != null) sb.append(" sib=" + mSib); 1389 if (mSic != null) sb.append(" sic=" + mSic); 1390 if (mPoundString != null) sb.append(" poundString=" + mPoundString); 1391 if (mDialingNumber != null) sb.append(" dialingNumber=" + mDialingNumber); 1392 if (mPwd != null) sb.append(" pwd=" + mPwd); 1393 sb.append("}"); 1394 return sb.toString(); 1395 } 1396 } 1397