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