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