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