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