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