1/* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.internal.telephony.gsm; 18 19import android.content.Context; 20import com.android.internal.telephony.*; 21 22import android.os.*; 23import android.telephony.PhoneNumberUtils; 24import android.text.SpannableStringBuilder; 25import android.text.TextUtils; 26import android.util.Log; 27 28import static com.android.internal.telephony.CommandsInterface.*; 29 30import java.util.regex.Pattern; 31import 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 */ 42public 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 (((serviceClass & CommandsInterface.SERVICE_CLASS_VOICE) != 0) || 600 (serviceClass == CommandsInterface.SERVICE_CLASS_NONE))) ? 1 : 0; 601 602 int isEnableDesired = 603 ((cfAction == CommandsInterface.CF_ACTION_ENABLE) || 604 (cfAction == CommandsInterface.CF_ACTION_REGISTRATION)) ? 1 : 0; 605 606 Log.d(LOG_TAG, "is CF setCallForward"); 607 phone.mCM.setCallForward(cfAction, reason, serviceClass, 608 dialingNumber, time, obtainMessage( 609 EVENT_SET_CFF_COMPLETE, 610 isSettingUnconditionalVoice, 611 isEnableDesired, this)); 612 } 613 } else if (isServiceCodeCallBarring(sc)) { 614 // sia = password 615 // sib = basic service group 616 617 String password = sia; 618 int serviceClass = siToServiceClass(sib); 619 String facility = scToBarringFacility(sc); 620 621 if (isInterrogate()) { 622 phone.mCM.queryFacilityLock(facility, password, 623 serviceClass, obtainMessage(EVENT_QUERY_COMPLETE, this)); 624 } else if (isActivate() || isDeactivate()) { 625 phone.mCM.setFacilityLock(facility, isActivate(), password, 626 serviceClass, obtainMessage(EVENT_SET_COMPLETE, this)); 627 } else { 628 throw new RuntimeException ("Invalid or Unsupported MMI Code"); 629 } 630 631 } else if (sc != null && sc.equals(SC_PWD)) { 632 // sia = fac 633 // sib = old pwd 634 // sic = new pwd 635 // pwd = new pwd 636 String facility; 637 String oldPwd = sib; 638 String newPwd = sic; 639 if (isActivate() || isRegister()) { 640 // Even though ACTIVATE is acceptable, this is really termed a REGISTER 641 action = ACTION_REGISTER; 642 643 if (sia == null) { 644 // If sc was not specified, treat it as BA_ALL. 645 facility = CommandsInterface.CB_FACILITY_BA_ALL; 646 } else { 647 facility = scToBarringFacility(sia); 648 } 649 if (newPwd.equals(pwd)) { 650 phone.mCM.changeBarringPassword(facility, oldPwd, 651 newPwd, obtainMessage(EVENT_SET_COMPLETE, this)); 652 } else { 653 // password mismatch; return error 654 handlePasswordError(com.android.internal.R.string.passwordIncorrect); 655 } 656 } else { 657 throw new RuntimeException ("Invalid or Unsupported MMI Code"); 658 } 659 660 } else if (sc != null && sc.equals(SC_WAIT)) { 661 // sia = basic service group 662 int serviceClass = siToServiceClass(sia); 663 664 if (isActivate() || isDeactivate()) { 665 phone.mCM.setCallWaiting(isActivate(), serviceClass, 666 obtainMessage(EVENT_SET_COMPLETE, this)); 667 } else if (isInterrogate()) { 668 phone.mCM.queryCallWaiting(serviceClass, 669 obtainMessage(EVENT_QUERY_COMPLETE, this)); 670 } else { 671 throw new RuntimeException ("Invalid or Unsupported MMI Code"); 672 } 673 } else if (isPinCommand()) { 674 // sia = old PIN or PUK 675 // sib = new PIN 676 // sic = new PIN 677 String oldPinOrPuk = sia; 678 String newPin = sib; 679 int pinLen = newPin.length(); 680 if (isRegister()) { 681 if (!newPin.equals(sic)) { 682 // password mismatch; return error 683 handlePasswordError(com.android.internal.R.string.mismatchPin); 684 } else if (pinLen < 4 || pinLen > 8 ) { 685 // invalid length 686 handlePasswordError(com.android.internal.R.string.invalidPin); 687 } else if (sc.equals(SC_PIN) && 688 phone.mSimCard.getState() == SimCard.State.PUK_REQUIRED ) { 689 // Sim is puk-locked 690 handlePasswordError(com.android.internal.R.string.needPuk); 691 } else { 692 // pre-checks OK 693 if (sc.equals(SC_PIN)) { 694 phone.mCM.changeIccPin(oldPinOrPuk, newPin, 695 obtainMessage(EVENT_SET_COMPLETE, this)); 696 } else if (sc.equals(SC_PIN2)) { 697 phone.mCM.changeIccPin2(oldPinOrPuk, newPin, 698 obtainMessage(EVENT_SET_COMPLETE, this)); 699 } else if (sc.equals(SC_PUK)) { 700 phone.mCM.supplyIccPuk(oldPinOrPuk, newPin, 701 obtainMessage(EVENT_SET_COMPLETE, this)); 702 } else if (sc.equals(SC_PUK2)) { 703 phone.mCM.supplyIccPuk2(oldPinOrPuk, newPin, 704 obtainMessage(EVENT_SET_COMPLETE, this)); 705 } 706 } 707 } else { 708 throw new RuntimeException ("Invalid or Unsupported MMI Code"); 709 } 710 } else if (poundString != null) { 711 sendUssd(poundString); 712 } else { 713 throw new RuntimeException ("Invalid or Unsupported MMI Code"); 714 } 715 } catch (RuntimeException exc) { 716 state = State.FAILED; 717 message = context.getText(com.android.internal.R.string.mmiError); 718 phone.onMMIDone(this); 719 } 720 } 721 722 private void handlePasswordError(int res) { 723 state = State.FAILED; 724 StringBuilder sb = new StringBuilder(getScString()); 725 sb.append("\n"); 726 sb.append(context.getText(res)); 727 message = sb; 728 phone.onMMIDone(this); 729 } 730 731 /** 732 * Called from GSMPhone 733 * 734 * An unsolicited USSD NOTIFY or REQUEST has come in matching 735 * up with this pending USSD request 736 * 737 * Note: If REQUEST, this exchange is complete, but the session remains 738 * active (ie, the network expects user input). 739 */ 740 void 741 onUssdFinished(String ussdMessage, boolean isUssdRequest) { 742 if (state == State.PENDING) { 743 if (ussdMessage == null) { 744 message = context.getText(com.android.internal.R.string.mmiComplete); 745 } else { 746 message = ussdMessage; 747 } 748 this.isUssdRequest = isUssdRequest; 749 // If it's a request, leave it PENDING so that it's cancelable. 750 if (!isUssdRequest) { 751 state = State.COMPLETE; 752 } 753 754 phone.onMMIDone(this); 755 } 756 } 757 758 /** 759 * Called from GSMPhone 760 * 761 * The radio has reset, and this is still pending 762 */ 763 764 void 765 onUssdFinishedError() { 766 if (state == State.PENDING) { 767 state = State.FAILED; 768 message = context.getText(com.android.internal.R.string.mmiError); 769 770 phone.onMMIDone(this); 771 } 772 } 773 774 void sendUssd(String ussdMessage) { 775 // Treat this as a USSD string 776 isPendingUSSD = true; 777 778 // Note that unlike most everything else, the USSD complete 779 // response does not complete this MMI code...we wait for 780 // an unsolicited USSD "Notify" or "Request". 781 // The matching up of this is doene in GSMPhone. 782 783 phone.mCM.sendUSSD(ussdMessage, 784 obtainMessage(EVENT_USSD_COMPLETE, this)); 785 } 786 787 /** Called from GSMPhone.handleMessage; not a Handler subclass */ 788 public void 789 handleMessage (Message msg) { 790 AsyncResult ar; 791 792 switch (msg.what) { 793 case EVENT_SET_COMPLETE: 794 ar = (AsyncResult) (msg.obj); 795 796 onSetComplete(ar); 797 break; 798 799 case EVENT_SET_CFF_COMPLETE: 800 ar = (AsyncResult) (msg.obj); 801 802 /* 803 * msg.arg1 = 1 means to set unconditional voice call forwarding 804 * msg.arg2 = 1 means to enable voice call forwarding 805 */ 806 if ((ar.exception == null) && (msg.arg1 == 1)) { 807 boolean cffEnabled = (msg.arg2 == 1); 808 phone.mSIMRecords.setVoiceCallForwardingFlag(1, cffEnabled); 809 } 810 811 onSetComplete(ar); 812 break; 813 814 case EVENT_GET_CLIR_COMPLETE: 815 ar = (AsyncResult) (msg.obj); 816 onGetClirComplete(ar); 817 break; 818 819 case EVENT_QUERY_CF_COMPLETE: 820 ar = (AsyncResult) (msg.obj); 821 onQueryCfComplete(ar); 822 break; 823 824 case EVENT_QUERY_COMPLETE: 825 ar = (AsyncResult) (msg.obj); 826 onQueryComplete(ar); 827 break; 828 829 case EVENT_USSD_COMPLETE: 830 ar = (AsyncResult) (msg.obj); 831 832 if (ar.exception != null) { 833 state = State.FAILED; 834 message = context.getText( 835 com.android.internal.R.string.mmiError); 836 837 phone.onMMIDone(this); 838 } 839 840 // Note that unlike most everything else, the USSD complete 841 // response does not complete this MMI code...we wait for 842 // an unsolicited USSD "Notify" or "Request". 843 // The matching up of this is done in GSMPhone. 844 845 break; 846 847 case EVENT_USSD_CANCEL_COMPLETE: 848 phone.onMMIDone(this); 849 break; 850 } 851 } 852 //***** Private instance methods 853 854 private CharSequence getScString() { 855 if (sc != null) { 856 if (isServiceCodeCallBarring(sc)) { 857 return context.getText(com.android.internal.R.string.BaMmi); 858 } else if (isServiceCodeCallForwarding(sc)) { 859 return context.getText(com.android.internal.R.string.CfMmi); 860 } else if (sc.equals(SC_CLIP)) { 861 return context.getText(com.android.internal.R.string.ClipMmi); 862 } else if (sc.equals(SC_CLIR)) { 863 return context.getText(com.android.internal.R.string.ClirMmi); 864 } else if (sc.equals(SC_PWD)) { 865 return context.getText(com.android.internal.R.string.PwdMmi); 866 } else if (sc.equals(SC_WAIT)) { 867 return context.getText(com.android.internal.R.string.CwMmi); 868 } else if (isPinCommand()) { 869 return context.getText(com.android.internal.R.string.PinMmi); 870 } 871 } 872 873 return ""; 874 } 875 876 private void 877 onSetComplete(AsyncResult ar){ 878 StringBuilder sb = new StringBuilder(getScString()); 879 sb.append("\n"); 880 881 if (ar.exception != null) { 882 state = State.FAILED; 883 if (ar.exception instanceof CommandException) { 884 CommandException.Error err = ((CommandException)(ar.exception)).getCommandError(); 885 if (err == CommandException.Error.PASSWORD_INCORRECT) { 886 if (isPinCommand()) { 887 // look specifically for the PUK commands and adjust 888 // the message accordingly. 889 if (sc.equals(SC_PUK) || sc.equals(SC_PUK2)) { 890 sb.append(context.getText( 891 com.android.internal.R.string.badPuk)); 892 } else { 893 sb.append(context.getText( 894 com.android.internal.R.string.badPin)); 895 } 896 } else { 897 sb.append(context.getText( 898 com.android.internal.R.string.passwordIncorrect)); 899 } 900 } else if (err == CommandException.Error.SIM_PUK2) { 901 sb.append(context.getText( 902 com.android.internal.R.string.badPin)); 903 sb.append("\n"); 904 sb.append(context.getText( 905 com.android.internal.R.string.needPuk2)); 906 } else { 907 sb.append(context.getText( 908 com.android.internal.R.string.mmiError)); 909 } 910 } else { 911 sb.append(context.getText( 912 com.android.internal.R.string.mmiError)); 913 } 914 } else if (isActivate()) { 915 state = State.COMPLETE; 916 sb.append(context.getText( 917 com.android.internal.R.string.serviceEnabled)); 918 // Record CLIR setting 919 if (sc.equals(SC_CLIR)) { 920 phone.saveClirSetting(CommandsInterface.CLIR_INVOCATION); 921 } 922 } else if (isDeactivate()) { 923 state = State.COMPLETE; 924 sb.append(context.getText( 925 com.android.internal.R.string.serviceDisabled)); 926 // Record CLIR setting 927 if (sc.equals(SC_CLIR)) { 928 phone.saveClirSetting(CommandsInterface.CLIR_SUPPRESSION); 929 } 930 } else if (isRegister()) { 931 state = State.COMPLETE; 932 sb.append(context.getText( 933 com.android.internal.R.string.serviceRegistered)); 934 } else if (isErasure()) { 935 state = State.COMPLETE; 936 sb.append(context.getText( 937 com.android.internal.R.string.serviceErased)); 938 } else { 939 state = State.FAILED; 940 sb.append(context.getText( 941 com.android.internal.R.string.mmiError)); 942 } 943 944 message = sb; 945 phone.onMMIDone(this); 946 } 947 948 private void 949 onGetClirComplete(AsyncResult ar) { 950 StringBuilder sb = new StringBuilder(getScString()); 951 sb.append("\n"); 952 953 if (ar.exception != null) { 954 state = State.FAILED; 955 sb.append(context.getText(com.android.internal.R.string.mmiError)); 956 } else { 957 int clirArgs[]; 958 959 clirArgs = (int[])ar.result; 960 961 // the 'm' parameter from TS 27.007 7.7 962 switch (clirArgs[1]) { 963 case 0: // CLIR not provisioned 964 sb.append(context.getText( 965 com.android.internal.R.string.serviceNotProvisioned)); 966 state = State.COMPLETE; 967 break; 968 969 case 1: // CLIR provisioned in permanent mode 970 sb.append(context.getText( 971 com.android.internal.R.string.CLIRPermanent)); 972 state = State.COMPLETE; 973 break; 974 975 case 2: // unknown (e.g. no network, etc.) 976 sb.append(context.getText( 977 com.android.internal.R.string.mmiError)); 978 state = State.FAILED; 979 break; 980 981 case 3: // CLIR temporary mode presentation restricted 982 983 // the 'n' parameter from TS 27.007 7.7 984 switch (clirArgs[0]) { 985 default: 986 case 0: // Default 987 sb.append(context.getText( 988 com.android.internal.R.string.CLIRDefaultOnNextCallOn)); 989 break; 990 case 1: // CLIR invocation 991 sb.append(context.getText( 992 com.android.internal.R.string.CLIRDefaultOnNextCallOn)); 993 break; 994 case 2: // CLIR suppression 995 sb.append(context.getText( 996 com.android.internal.R.string.CLIRDefaultOnNextCallOff)); 997 break; 998 } 999 state = State.COMPLETE; 1000 break; 1001 1002 case 4: // CLIR temporary mode presentation allowed 1003 // the 'n' parameter from TS 27.007 7.7 1004 switch (clirArgs[0]) { 1005 default: 1006 case 0: // Default 1007 sb.append(context.getText( 1008 com.android.internal.R.string.CLIRDefaultOffNextCallOff)); 1009 break; 1010 case 1: // CLIR invocation 1011 sb.append(context.getText( 1012 com.android.internal.R.string.CLIRDefaultOffNextCallOn)); 1013 break; 1014 case 2: // CLIR suppression 1015 sb.append(context.getText( 1016 com.android.internal.R.string.CLIRDefaultOffNextCallOff)); 1017 break; 1018 } 1019 1020 state = State.COMPLETE; 1021 break; 1022 } 1023 } 1024 1025 message = sb; 1026 phone.onMMIDone(this); 1027 } 1028 1029 /** 1030 * @param serviceClass 1 bit of the service class bit vectory 1031 * @return String to be used for call forward query MMI response text. 1032 * Returns null if unrecognized 1033 */ 1034 1035 private CharSequence 1036 serviceClassToCFString (int serviceClass) { 1037 switch (serviceClass) { 1038 case SERVICE_CLASS_VOICE: 1039 return context.getText(com.android.internal.R.string.serviceClassVoice); 1040 case SERVICE_CLASS_DATA: 1041 return context.getText(com.android.internal.R.string.serviceClassData); 1042 case SERVICE_CLASS_FAX: 1043 return context.getText(com.android.internal.R.string.serviceClassFAX); 1044 case SERVICE_CLASS_SMS: 1045 return context.getText(com.android.internal.R.string.serviceClassSMS); 1046 case SERVICE_CLASS_DATA_SYNC: 1047 return context.getText(com.android.internal.R.string.serviceClassDataSync); 1048 case SERVICE_CLASS_DATA_ASYNC: 1049 return context.getText(com.android.internal.R.string.serviceClassDataAsync); 1050 case SERVICE_CLASS_PACKET: 1051 return context.getText(com.android.internal.R.string.serviceClassPacket); 1052 case SERVICE_CLASS_PAD: 1053 return context.getText(com.android.internal.R.string.serviceClassPAD); 1054 default: 1055 return null; 1056 } 1057 } 1058 1059 1060 /** one CallForwardInfo + serviceClassMask -> one line of text */ 1061 private CharSequence 1062 makeCFQueryResultMessage(CallForwardInfo info, int serviceClassMask) { 1063 CharSequence template; 1064 String sources[] = {"{0}", "{1}", "{2}"}; 1065 CharSequence destinations[] = new CharSequence[3]; 1066 boolean needTimeTemplate; 1067 1068 // CF_REASON_NO_REPLY also has a time value associated with 1069 // it. All others don't. 1070 1071 needTimeTemplate = 1072 (info.reason == CommandsInterface.CF_REASON_NO_REPLY); 1073 1074 if (info.status == 1) { 1075 if (needTimeTemplate) { 1076 template = context.getText( 1077 com.android.internal.R.string.cfTemplateForwardedTime); 1078 } else { 1079 template = context.getText( 1080 com.android.internal.R.string.cfTemplateForwarded); 1081 } 1082 } else if (info.status == 0 && isEmptyOrNull(info.number)) { 1083 template = context.getText( 1084 com.android.internal.R.string.cfTemplateNotForwarded); 1085 } else { /* (info.status == 0) && !isEmptyOrNull(info.number) */ 1086 // A call forward record that is not active but contains 1087 // a phone number is considered "registered" 1088 1089 if (needTimeTemplate) { 1090 template = context.getText( 1091 com.android.internal.R.string.cfTemplateRegisteredTime); 1092 } else { 1093 template = context.getText( 1094 com.android.internal.R.string.cfTemplateRegistered); 1095 } 1096 } 1097 1098 // In the template (from strings.xmls) 1099 // {0} is one of "bearerServiceCode*" 1100 // {1} is dialing number 1101 // {2} is time in seconds 1102 1103 destinations[0] = serviceClassToCFString(info.serviceClass & serviceClassMask); 1104 destinations[1] = PhoneNumberUtils.stringFromStringAndTOA(info.number, info.toa); 1105 destinations[2] = Integer.toString(info.timeSeconds); 1106 1107 if (info.reason == CommandsInterface.CF_REASON_UNCONDITIONAL && 1108 (info.serviceClass & serviceClassMask) 1109 == CommandsInterface.SERVICE_CLASS_VOICE) { 1110 boolean cffEnabled = (info.status == 1); 1111 phone.mSIMRecords.setVoiceCallForwardingFlag(1, cffEnabled); 1112 } 1113 1114 return TextUtils.replace(template, sources, destinations); 1115 } 1116 1117 1118 private void 1119 onQueryCfComplete(AsyncResult ar) { 1120 StringBuilder sb = new StringBuilder(getScString()); 1121 sb.append("\n"); 1122 1123 if (ar.exception != null) { 1124 state = State.FAILED; 1125 sb.append(context.getText(com.android.internal.R.string.mmiError)); 1126 } else { 1127 CallForwardInfo infos[]; 1128 1129 infos = (CallForwardInfo[]) ar.result; 1130 1131 if (infos.length == 0) { 1132 // Assume the default is not active 1133 sb.append(context.getText(com.android.internal.R.string.serviceDisabled)); 1134 1135 // Set unconditional CFF in SIM to false 1136 phone.mSIMRecords.setVoiceCallForwardingFlag(1, false); 1137 } else { 1138 1139 SpannableStringBuilder tb = new SpannableStringBuilder(); 1140 1141 // Each bit in the service class gets its own result line 1142 // The service classes may be split up over multiple 1143 // CallForwardInfos. So, for each service classs, find out 1144 // which CallForwardInfo represents it and then build 1145 // the response text based on that 1146 1147 for (int serviceClassMask = 1 1148 ; serviceClassMask <= SERVICE_CLASS_MAX 1149 ; serviceClassMask <<= 1 1150 ) { 1151 for (int i = 0, s = infos.length; i < s ; i++) { 1152 if ((serviceClassMask & infos[i].serviceClass) != 0) { 1153 tb.append(makeCFQueryResultMessage(infos[i], 1154 serviceClassMask)); 1155 tb.append("\n"); 1156 } 1157 } 1158 } 1159 sb.append(tb); 1160 } 1161 1162 state = State.COMPLETE; 1163 } 1164 1165 message = sb; 1166 phone.onMMIDone(this); 1167 1168 } 1169 1170 private void 1171 onQueryComplete(AsyncResult ar) { 1172 StringBuilder sb = new StringBuilder(getScString()); 1173 sb.append("\n"); 1174 1175 if (ar.exception != null) { 1176 state = State.FAILED; 1177 sb.append(context.getText(com.android.internal.R.string.mmiError)); 1178 } else { 1179 int[] ints = (int[])ar.result; 1180 1181 if (ints.length != 0) { 1182 if (ints[0] == 0) { 1183 sb.append(context.getText(com.android.internal.R.string.serviceDisabled)); 1184 } else if (sc.equals(SC_WAIT)) { 1185 // Call Waiting includes additional data in the response. 1186 sb.append(createQueryCallWaitingResultMessage(ints[1])); 1187 } else if (isServiceCodeCallBarring(sc)) { 1188 // ints[0] for Call Barring is a bit vector of services 1189 sb.append(createQueryCallBarringResultMessage(ints[0])); 1190 } else if (ints[0] == 1) { 1191 // for all other services, treat it as a boolean 1192 sb.append(context.getText(com.android.internal.R.string.serviceEnabled)); 1193 } else { 1194 sb.append(context.getText(com.android.internal.R.string.mmiError)); 1195 } 1196 } else { 1197 sb.append(context.getText(com.android.internal.R.string.mmiError)); 1198 } 1199 state = State.COMPLETE; 1200 } 1201 1202 message = sb; 1203 phone.onMMIDone(this); 1204 } 1205 1206 private CharSequence 1207 createQueryCallWaitingResultMessage(int serviceClass) { 1208 StringBuilder sb = 1209 new StringBuilder(context.getText(com.android.internal.R.string.serviceEnabledFor)); 1210 1211 for (int classMask = 1 1212 ; classMask <= SERVICE_CLASS_MAX 1213 ; classMask <<= 1 1214 ) { 1215 if ((classMask & serviceClass) != 0) { 1216 sb.append("\n"); 1217 sb.append(serviceClassToCFString(classMask & serviceClass)); 1218 } 1219 } 1220 return sb; 1221 } 1222 private CharSequence 1223 createQueryCallBarringResultMessage(int serviceClass) 1224 { 1225 StringBuilder sb = new StringBuilder(context.getText(com.android.internal.R.string.serviceEnabledFor)); 1226 1227 for (int classMask = 1 1228 ; classMask <= SERVICE_CLASS_MAX 1229 ; classMask <<= 1 1230 ) { 1231 if ((classMask & serviceClass) != 0) { 1232 sb.append("\n"); 1233 sb.append(serviceClassToCFString(classMask & serviceClass)); 1234 } 1235 } 1236 return sb; 1237 } 1238 1239 /*** 1240 * TODO: It would be nice to have a method here that can take in a dialstring and 1241 * figure out if there is an MMI code embedded within it. This code would replace 1242 * some of the string parsing functionality in the Phone App's 1243 * SpecialCharSequenceMgr class. 1244 */ 1245 1246} 1247