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