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