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