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