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(), 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 static private boolean isTwoDigitShortCode(Context context, String dialString)716 isTwoDigitShortCode(Context context, String dialString) { 717 Rlog.d(LOG_TAG, "isTwoDigitShortCode"); 718 719 if (dialString == null || dialString.length() > 2) return false; 720 721 if (sTwoDigitNumberPattern == null) { 722 sTwoDigitNumberPattern = context.getResources().getStringArray( 723 com.android.internal.R.array.config_twoDigitNumberPattern); 724 } 725 726 for (String dialnumber : sTwoDigitNumberPattern) { 727 Rlog.d(LOG_TAG, "Two Digit Number Pattern " + dialnumber); 728 if (dialString.equals(dialnumber)) { 729 Rlog.d(LOG_TAG, "Two Digit Number Pattern -true"); 730 return true; 731 } 732 } 733 Rlog.d(LOG_TAG, "Two Digit Number Pattern -false"); 734 return false; 735 } 736 737 /** 738 * Helper function for newFromDialString. Returns true if dialString appears 739 * to be a short code AND conditions are correct for it to be treated as 740 * such. 741 */ isShortCode(String dialString, GsmCdmaPhone phone)742 static private boolean isShortCode(String dialString, GsmCdmaPhone phone) { 743 // Refer to TS 22.030 Figure 3.5.3.2: 744 if (dialString == null) { 745 return false; 746 } 747 748 // Illegal dial string characters will give a ZERO length. 749 // At this point we do not want to crash as any application with 750 // call privileges may send a non dial string. 751 // It return false as when the dialString is equal to NULL. 752 if (dialString.length() == 0) { 753 return false; 754 } 755 756 if (PhoneNumberUtils.isLocalEmergencyNumber(phone.getContext(), dialString)) { 757 return false; 758 } else { 759 return isShortCodeUSSD(dialString, phone); 760 } 761 } 762 763 /** 764 * Helper function for isShortCode. Returns true if dialString appears to be 765 * a short code and it is a USSD structure 766 * 767 * According to the 3PGG TS 22.030 specification Figure 3.5.3.2: A 1 or 2 768 * digit "short code" is treated as USSD if it is entered while on a call or 769 * does not satisfy the condition (exactly 2 digits && starts with '1'), there 770 * are however exceptions to this rule (see below) 771 * 772 * Exception (1) to Call initiation is: If the user of the device is already in a call 773 * and enters a Short String without any #-key at the end and the length of the Short String is 774 * equal or less then the MAX_LENGTH_SHORT_CODE [constant that is equal to 2] 775 * 776 * The phone shall initiate a USSD/SS commands. 777 */ isShortCodeUSSD(String dialString, GsmCdmaPhone phone)778 static private boolean isShortCodeUSSD(String dialString, GsmCdmaPhone phone) { 779 if (dialString != null && dialString.length() <= MAX_LENGTH_SHORT_CODE) { 780 if (phone.isInCall()) { 781 return true; 782 } 783 784 if (dialString.length() != MAX_LENGTH_SHORT_CODE || 785 dialString.charAt(0) != '1') { 786 return true; 787 } 788 } 789 return false; 790 } 791 792 /** 793 * @return true if the Service Code is PIN/PIN2/PUK/PUK2-related 794 */ isPinPukCommand()795 public boolean isPinPukCommand() { 796 return mSc != null && (mSc.equals(SC_PIN) || mSc.equals(SC_PIN2) 797 || mSc.equals(SC_PUK) || mSc.equals(SC_PUK2)); 798 } 799 800 /** 801 * See TS 22.030 Annex B. 802 * In temporary mode, to suppress CLIR for a single call, enter: 803 * " * 31 # [called number] SEND " 804 * In temporary mode, to invoke CLIR for a single call enter: 805 * " # 31 # [called number] SEND " 806 */ 807 @UnsupportedAppUsage 808 public boolean isTemporaryModeCLIR()809 isTemporaryModeCLIR() { 810 return mSc != null && mSc.equals(SC_CLIR) && mDialingNumber != null 811 && (isActivate() || isDeactivate()); 812 } 813 814 /** 815 * returns CommandsInterface.CLIR_* 816 * See also isTemporaryModeCLIR() 817 */ 818 @UnsupportedAppUsage 819 public int getCLIRMode()820 getCLIRMode() { 821 if (mSc != null && mSc.equals(SC_CLIR)) { 822 if (isActivate()) { 823 return CommandsInterface.CLIR_SUPPRESSION; 824 } else if (isDeactivate()) { 825 return CommandsInterface.CLIR_INVOCATION; 826 } 827 } 828 829 return CommandsInterface.CLIR_DEFAULT; 830 } 831 832 /** 833 * Returns true if the Service Code is FAC to dial as a normal call. 834 * 835 * FAC stands for feature access code and it is special patterns of characters 836 * to invoke certain features. 837 */ isFacToDial()838 private boolean isFacToDial() { 839 CarrierConfigManager configManager = (CarrierConfigManager) 840 mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 841 PersistableBundle b = configManager.getConfigForSubId(mPhone.getSubId()); 842 if (b != null) { 843 String[] dialFacList = b.getStringArray(CarrierConfigManager 844 .KEY_FEATURE_ACCESS_CODES_STRING_ARRAY); 845 if (!ArrayUtils.isEmpty(dialFacList)) { 846 for (String fac : dialFacList) { 847 if (fac.equals(mSc)) { 848 return true; 849 } 850 } 851 } 852 } 853 return false; 854 } 855 856 @UnsupportedAppUsage isActivate()857 boolean isActivate() { 858 return mAction != null && mAction.equals(ACTION_ACTIVATE); 859 } 860 861 @UnsupportedAppUsage isDeactivate()862 boolean isDeactivate() { 863 return mAction != null && mAction.equals(ACTION_DEACTIVATE); 864 } 865 866 @UnsupportedAppUsage isInterrogate()867 boolean isInterrogate() { 868 return mAction != null && mAction.equals(ACTION_INTERROGATE); 869 } 870 871 @UnsupportedAppUsage isRegister()872 boolean isRegister() { 873 return mAction != null && mAction.equals(ACTION_REGISTER); 874 } 875 876 @UnsupportedAppUsage isErasure()877 boolean isErasure() { 878 return mAction != null && mAction.equals(ACTION_ERASURE); 879 } 880 881 /** 882 * Returns true if this is a USSD code that's been submitted to the 883 * network...eg, after processCode() is called 884 */ isPendingUSSD()885 public boolean isPendingUSSD() { 886 return mIsPendingUSSD; 887 } 888 889 @Override isUssdRequest()890 public boolean isUssdRequest() { 891 return mIsUssdRequest; 892 } 893 isSsInfo()894 public boolean isSsInfo() { 895 return mIsSsInfo; 896 } 897 isVoiceUnconditionalForwarding(int reason, int serviceClass)898 public static boolean isVoiceUnconditionalForwarding(int reason, int serviceClass) { 899 return (((reason == CommandsInterface.CF_REASON_UNCONDITIONAL) 900 || (reason == CommandsInterface.CF_REASON_ALL)) 901 && (((serviceClass & CommandsInterface.SERVICE_CLASS_VOICE) != 0) 902 || (serviceClass == CommandsInterface.SERVICE_CLASS_NONE))); 903 } 904 905 /** Process a MMI code or short code...anything that isn't a dialing number */ 906 @UnsupportedAppUsage 907 public void processCode()908 processCode() throws CallStateException { 909 try { 910 if (isShortCode()) { 911 Rlog.d(LOG_TAG, "processCode: isShortCode"); 912 // These just get treated as USSD. 913 sendUssd(mDialingNumber); 914 } else if (mDialingNumber != null) { 915 // We should have no dialing numbers here 916 throw new RuntimeException ("Invalid or Unsupported MMI Code"); 917 } else if (mSc != null && mSc.equals(SC_CLIP)) { 918 Rlog.d(LOG_TAG, "processCode: is CLIP"); 919 if (isInterrogate()) { 920 mPhone.mCi.queryCLIP( 921 obtainMessage(EVENT_QUERY_COMPLETE, this)); 922 } else { 923 throw new RuntimeException ("Invalid or Unsupported MMI Code"); 924 } 925 } else if (mSc != null && mSc.equals(SC_CLIR)) { 926 Rlog.d(LOG_TAG, "processCode: is CLIR"); 927 if (isActivate() && !mPhone.isClirActivationAndDeactivationPrevented()) { 928 mPhone.mCi.setCLIR(CommandsInterface.CLIR_INVOCATION, 929 obtainMessage(EVENT_SET_COMPLETE, this)); 930 } else if (isDeactivate() && !mPhone.isClirActivationAndDeactivationPrevented()) { 931 mPhone.mCi.setCLIR(CommandsInterface.CLIR_SUPPRESSION, 932 obtainMessage(EVENT_SET_COMPLETE, this)); 933 } else if (isInterrogate()) { 934 mPhone.mCi.getCLIR( 935 obtainMessage(EVENT_GET_CLIR_COMPLETE, this)); 936 } else { 937 throw new RuntimeException ("Invalid or Unsupported MMI Code"); 938 } 939 } else if (isServiceCodeCallForwarding(mSc)) { 940 Rlog.d(LOG_TAG, "processCode: is CF"); 941 942 String dialingNumber = mSia; 943 int serviceClass = siToServiceClass(mSib); 944 int reason = scToCallForwardReason(mSc); 945 int time = siToTime(mSic); 946 947 if (isInterrogate()) { 948 mPhone.mCi.queryCallForwardStatus( 949 reason, serviceClass, dialingNumber, 950 obtainMessage(EVENT_QUERY_CF_COMPLETE, this)); 951 } else { 952 int cfAction; 953 954 if (isActivate()) { 955 // 3GPP TS 22.030 6.5.2 956 // a call forwarding request with a single * would be 957 // interpreted as registration if containing a forwarded-to 958 // number, or an activation if not 959 if (isEmptyOrNull(dialingNumber)) { 960 cfAction = CommandsInterface.CF_ACTION_ENABLE; 961 mIsCallFwdReg = false; 962 } else { 963 cfAction = CommandsInterface.CF_ACTION_REGISTRATION; 964 mIsCallFwdReg = true; 965 } 966 } else if (isDeactivate()) { 967 cfAction = CommandsInterface.CF_ACTION_DISABLE; 968 } else if (isRegister()) { 969 cfAction = CommandsInterface.CF_ACTION_REGISTRATION; 970 } else if (isErasure()) { 971 cfAction = CommandsInterface.CF_ACTION_ERASURE; 972 } else { 973 throw new RuntimeException ("invalid action"); 974 } 975 976 int isEnableDesired = 977 ((cfAction == CommandsInterface.CF_ACTION_ENABLE) || 978 (cfAction == CommandsInterface.CF_ACTION_REGISTRATION)) ? 1 : 0; 979 980 Rlog.d(LOG_TAG, "processCode: is CF setCallForward"); 981 mPhone.mCi.setCallForward(cfAction, reason, serviceClass, 982 dialingNumber, time, obtainMessage( 983 EVENT_SET_CFF_COMPLETE, 984 isVoiceUnconditionalForwarding(reason, serviceClass) ? 1 : 0, 985 isEnableDesired, this)); 986 } 987 } else if (isServiceCodeCallBarring(mSc)) { 988 // sia = password 989 // sib = basic service group 990 991 String password = mSia; 992 int serviceClass = siToServiceClass(mSib); 993 String facility = scToBarringFacility(mSc); 994 995 if (isInterrogate()) { 996 mPhone.mCi.queryFacilityLock(facility, password, 997 serviceClass, obtainMessage(EVENT_QUERY_COMPLETE, this)); 998 } else if (isActivate() || isDeactivate()) { 999 mPhone.mCi.setFacilityLock(facility, isActivate(), password, 1000 serviceClass, obtainMessage(EVENT_SET_COMPLETE, this)); 1001 } else { 1002 throw new RuntimeException ("Invalid or Unsupported MMI Code"); 1003 } 1004 1005 } else if (mSc != null && mSc.equals(SC_PWD)) { 1006 // sia = fac 1007 // sib = old pwd 1008 // sic = new pwd 1009 // pwd = new pwd 1010 String facility; 1011 String oldPwd = mSib; 1012 String newPwd = mSic; 1013 if (isActivate() || isRegister()) { 1014 // Even though ACTIVATE is acceptable, this is really termed a REGISTER 1015 mAction = ACTION_REGISTER; 1016 1017 if (mSia == null) { 1018 // If sc was not specified, treat it as BA_ALL. 1019 facility = CommandsInterface.CB_FACILITY_BA_ALL; 1020 } else { 1021 facility = scToBarringFacility(mSia); 1022 } 1023 if (newPwd.equals(mPwd)) { 1024 mPhone.mCi.changeBarringPassword(facility, oldPwd, 1025 newPwd, obtainMessage(EVENT_SET_COMPLETE, this)); 1026 } else { 1027 // password mismatch; return error 1028 handlePasswordError(com.android.internal.R.string.passwordIncorrect); 1029 } 1030 } else { 1031 throw new RuntimeException ("Invalid or Unsupported MMI Code"); 1032 } 1033 1034 } else if (mSc != null && mSc.equals(SC_WAIT)) { 1035 // sia = basic service group 1036 int serviceClass = siToServiceClass(mSia); 1037 1038 if (isActivate() || isDeactivate()) { 1039 mPhone.mCi.setCallWaiting(isActivate(), serviceClass, 1040 obtainMessage(EVENT_SET_COMPLETE, this)); 1041 } else if (isInterrogate()) { 1042 mPhone.mCi.queryCallWaiting(serviceClass, 1043 obtainMessage(EVENT_QUERY_COMPLETE, this)); 1044 } else { 1045 throw new RuntimeException ("Invalid or Unsupported MMI Code"); 1046 } 1047 } else if (isPinPukCommand()) { 1048 // TODO: This is the same as the code in CmdaMmiCode.java, 1049 // MmiCode should be an abstract or base class and this and 1050 // other common variables and code should be promoted. 1051 1052 // sia = old PIN or PUK 1053 // sib = new PIN 1054 // sic = new PIN 1055 String oldPinOrPuk = mSia; 1056 String newPinOrPuk = mSib; 1057 int pinLen = newPinOrPuk.length(); 1058 if (isRegister()) { 1059 if (!newPinOrPuk.equals(mSic)) { 1060 // password mismatch; return error 1061 handlePasswordError(com.android.internal.R.string.mismatchPin); 1062 } else if (pinLen < 4 || pinLen > 8 ) { 1063 // invalid length 1064 handlePasswordError(com.android.internal.R.string.invalidPin); 1065 } else if (mSc.equals(SC_PIN) 1066 && mUiccApplication != null 1067 && mUiccApplication.getState() == AppState.APPSTATE_PUK) { 1068 // Sim is puk-locked 1069 handlePasswordError(com.android.internal.R.string.needPuk); 1070 } else if (mUiccApplication != null) { 1071 Rlog.d(LOG_TAG, 1072 "processCode: process mmi service code using UiccApp sc=" + mSc); 1073 1074 // We have an app and the pre-checks are OK 1075 if (mSc.equals(SC_PIN)) { 1076 mUiccApplication.changeIccLockPassword(oldPinOrPuk, newPinOrPuk, 1077 obtainMessage(EVENT_SET_COMPLETE, this)); 1078 } else if (mSc.equals(SC_PIN2)) { 1079 mUiccApplication.changeIccFdnPassword(oldPinOrPuk, newPinOrPuk, 1080 obtainMessage(EVENT_SET_COMPLETE, this)); 1081 } else if (mSc.equals(SC_PUK)) { 1082 mUiccApplication.supplyPuk(oldPinOrPuk, newPinOrPuk, 1083 obtainMessage(EVENT_SET_COMPLETE, this)); 1084 } else if (mSc.equals(SC_PUK2)) { 1085 mUiccApplication.supplyPuk2(oldPinOrPuk, newPinOrPuk, 1086 obtainMessage(EVENT_SET_COMPLETE, this)); 1087 } else { 1088 throw new RuntimeException("uicc unsupported service code=" + mSc); 1089 } 1090 } else { 1091 throw new RuntimeException("No application mUiccApplicaiton is null"); 1092 } 1093 } else { 1094 throw new RuntimeException ("Ivalid register/action=" + mAction); 1095 } 1096 } else if (mPoundString != null) { 1097 sendUssd(mPoundString); 1098 } else { 1099 Rlog.d(LOG_TAG, "processCode: Invalid or Unsupported MMI Code"); 1100 throw new RuntimeException ("Invalid or Unsupported MMI Code"); 1101 } 1102 } catch (RuntimeException exc) { 1103 mState = State.FAILED; 1104 mMessage = mContext.getText(com.android.internal.R.string.mmiError); 1105 Rlog.d(LOG_TAG, "processCode: RuntimeException=" + exc); 1106 mPhone.onMMIDone(this); 1107 } 1108 } 1109 handlePasswordError(int res)1110 private void handlePasswordError(int res) { 1111 mState = State.FAILED; 1112 StringBuilder sb = new StringBuilder(getScString()); 1113 sb.append("\n"); 1114 sb.append(mContext.getText(res)); 1115 mMessage = sb; 1116 mPhone.onMMIDone(this); 1117 } 1118 1119 /** 1120 * Called from GsmCdmaPhone 1121 * 1122 * An unsolicited USSD NOTIFY or REQUEST has come in matching 1123 * up with this pending USSD request 1124 * 1125 * Note: If REQUEST, this exchange is complete, but the session remains 1126 * active (ie, the network expects user input). 1127 */ 1128 public void onUssdFinished(String ussdMessage, boolean isUssdRequest)1129 onUssdFinished(String ussdMessage, boolean isUssdRequest) { 1130 if (mState == State.PENDING) { 1131 if (TextUtils.isEmpty(ussdMessage)) { 1132 Rlog.d(LOG_TAG, "onUssdFinished: no network provided message; using default."); 1133 mMessage = mContext.getText(com.android.internal.R.string.mmiComplete); 1134 } else { 1135 mMessage = ussdMessage; 1136 } 1137 mIsUssdRequest = isUssdRequest; 1138 // If it's a request, leave it PENDING so that it's cancelable. 1139 if (!isUssdRequest) { 1140 mState = State.COMPLETE; 1141 } 1142 Rlog.d(LOG_TAG, "onUssdFinished: ussdMessage=" + ussdMessage); 1143 mPhone.onMMIDone(this); 1144 } 1145 } 1146 1147 /** 1148 * Called from GsmCdmaPhone 1149 * 1150 * The radio has reset, and this is still pending 1151 */ 1152 1153 public void onUssdFinishedError()1154 onUssdFinishedError() { 1155 if (mState == State.PENDING) { 1156 mState = State.FAILED; 1157 mMessage = mContext.getText(com.android.internal.R.string.mmiError); 1158 Rlog.d(LOG_TAG, "onUssdFinishedError"); 1159 mPhone.onMMIDone(this); 1160 } 1161 } 1162 1163 /** 1164 * Called from GsmCdmaPhone 1165 * 1166 * An unsolicited USSD NOTIFY or REQUEST has come in matching 1167 * up with this pending USSD request 1168 * 1169 * Note: If REQUEST, this exchange is complete, but the session remains 1170 * active (ie, the network expects user input). 1171 */ 1172 public void onUssdRelease()1173 onUssdRelease() { 1174 if (mState == State.PENDING) { 1175 mState = State.COMPLETE; 1176 mMessage = null; 1177 Rlog.d(LOG_TAG, "onUssdRelease"); 1178 mPhone.onMMIDone(this); 1179 } 1180 } 1181 sendUssd(String ussdMessage)1182 public void sendUssd(String ussdMessage) { 1183 // Treat this as a USSD string 1184 mIsPendingUSSD = true; 1185 1186 // Note that unlike most everything else, the USSD complete 1187 // response does not complete this MMI code...we wait for 1188 // an unsolicited USSD "Notify" or "Request". 1189 // The matching up of this is done in GsmCdmaPhone. 1190 mPhone.mCi.sendUSSD(ussdMessage, 1191 obtainMessage(EVENT_USSD_COMPLETE, this)); 1192 } 1193 1194 /** Called from GsmCdmaPhone.handleMessage; not a Handler subclass */ 1195 @Override 1196 public void handleMessage(Message msg)1197 handleMessage (Message msg) { 1198 AsyncResult ar; 1199 1200 switch (msg.what) { 1201 case EVENT_SET_COMPLETE: 1202 ar = (AsyncResult) (msg.obj); 1203 1204 onSetComplete(msg, ar); 1205 break; 1206 1207 case EVENT_SET_CFF_COMPLETE: 1208 ar = (AsyncResult) (msg.obj); 1209 1210 /* 1211 * msg.arg1 = 1 means to set unconditional voice call forwarding 1212 * msg.arg2 = 1 means to enable voice call forwarding 1213 */ 1214 if ((ar.exception == null) && (msg.arg1 == 1)) { 1215 boolean cffEnabled = (msg.arg2 == 1); 1216 mPhone.setVoiceCallForwardingFlag(1, cffEnabled, mDialingNumber); 1217 } 1218 1219 onSetComplete(msg, ar); 1220 break; 1221 1222 case EVENT_GET_CLIR_COMPLETE: 1223 ar = (AsyncResult) (msg.obj); 1224 onGetClirComplete(ar); 1225 break; 1226 1227 case EVENT_QUERY_CF_COMPLETE: 1228 ar = (AsyncResult) (msg.obj); 1229 onQueryCfComplete(ar); 1230 break; 1231 1232 case EVENT_QUERY_COMPLETE: 1233 ar = (AsyncResult) (msg.obj); 1234 onQueryComplete(ar); 1235 break; 1236 1237 case EVENT_USSD_COMPLETE: 1238 ar = (AsyncResult) (msg.obj); 1239 1240 if (ar.exception != null) { 1241 mState = State.FAILED; 1242 mMessage = getErrorMessage(ar); 1243 1244 mPhone.onMMIDone(this); 1245 } 1246 1247 // Note that unlike most everything else, the USSD complete 1248 // response does not complete this MMI code...we wait for 1249 // an unsolicited USSD "Notify" or "Request". 1250 // The matching up of this is done in GsmCdmaPhone. 1251 1252 break; 1253 1254 case EVENT_USSD_CANCEL_COMPLETE: 1255 mPhone.onMMIDone(this); 1256 break; 1257 } 1258 } 1259 //***** Private instance methods 1260 getErrorMessage(AsyncResult ar)1261 private CharSequence getErrorMessage(AsyncResult ar) { 1262 1263 if (ar.exception instanceof CommandException) { 1264 CommandException.Error err = ((CommandException)(ar.exception)).getCommandError(); 1265 if (err == CommandException.Error.FDN_CHECK_FAILURE) { 1266 Rlog.i(LOG_TAG, "FDN_CHECK_FAILURE"); 1267 return mContext.getText(com.android.internal.R.string.mmiFdnError); 1268 } else if (err == CommandException.Error.USSD_MODIFIED_TO_DIAL) { 1269 Rlog.i(LOG_TAG, "USSD_MODIFIED_TO_DIAL"); 1270 return mContext.getText(com.android.internal.R.string.stk_cc_ussd_to_dial); 1271 } else if (err == CommandException.Error.USSD_MODIFIED_TO_SS) { 1272 Rlog.i(LOG_TAG, "USSD_MODIFIED_TO_SS"); 1273 return mContext.getText(com.android.internal.R.string.stk_cc_ussd_to_ss); 1274 } else if (err == CommandException.Error.USSD_MODIFIED_TO_USSD) { 1275 Rlog.i(LOG_TAG, "USSD_MODIFIED_TO_USSD"); 1276 return mContext.getText(com.android.internal.R.string.stk_cc_ussd_to_ussd); 1277 } else if (err == CommandException.Error.SS_MODIFIED_TO_DIAL) { 1278 Rlog.i(LOG_TAG, "SS_MODIFIED_TO_DIAL"); 1279 return mContext.getText(com.android.internal.R.string.stk_cc_ss_to_dial); 1280 } else if (err == CommandException.Error.SS_MODIFIED_TO_USSD) { 1281 Rlog.i(LOG_TAG, "SS_MODIFIED_TO_USSD"); 1282 return mContext.getText(com.android.internal.R.string.stk_cc_ss_to_ussd); 1283 } else if (err == CommandException.Error.SS_MODIFIED_TO_SS) { 1284 Rlog.i(LOG_TAG, "SS_MODIFIED_TO_SS"); 1285 return mContext.getText(com.android.internal.R.string.stk_cc_ss_to_ss); 1286 } else if (err == CommandException.Error.OEM_ERROR_1) { 1287 Rlog.i(LOG_TAG, "OEM_ERROR_1 USSD_MODIFIED_TO_DIAL_VIDEO"); 1288 return mContext.getText(com.android.internal.R.string.stk_cc_ussd_to_dial_video); 1289 } 1290 } 1291 1292 return mContext.getText(com.android.internal.R.string.mmiError); 1293 } 1294 1295 @UnsupportedAppUsage getScString()1296 private CharSequence getScString() { 1297 if (mSc != null) { 1298 if (isServiceCodeCallBarring(mSc)) { 1299 return mContext.getText(com.android.internal.R.string.BaMmi); 1300 } else if (isServiceCodeCallForwarding(mSc)) { 1301 return mContext.getText(com.android.internal.R.string.CfMmi); 1302 } else if (mSc.equals(SC_CLIP)) { 1303 return mContext.getText(com.android.internal.R.string.ClipMmi); 1304 } else if (mSc.equals(SC_CLIR)) { 1305 return mContext.getText(com.android.internal.R.string.ClirMmi); 1306 } else if (mSc.equals(SC_PWD)) { 1307 return mContext.getText(com.android.internal.R.string.PwdMmi); 1308 } else if (mSc.equals(SC_WAIT)) { 1309 return mContext.getText(com.android.internal.R.string.CwMmi); 1310 } else if (isPinPukCommand()) { 1311 return mContext.getText(com.android.internal.R.string.PinMmi); 1312 } 1313 } 1314 1315 return ""; 1316 } 1317 1318 private void onSetComplete(Message msg, AsyncResult ar)1319 onSetComplete(Message msg, AsyncResult ar){ 1320 StringBuilder sb = new StringBuilder(getScString()); 1321 sb.append("\n"); 1322 1323 if (ar.exception != null) { 1324 mState = State.FAILED; 1325 if (ar.exception instanceof CommandException) { 1326 CommandException.Error err = ((CommandException)(ar.exception)).getCommandError(); 1327 if (err == CommandException.Error.PASSWORD_INCORRECT) { 1328 if (isPinPukCommand()) { 1329 // look specifically for the PUK commands and adjust 1330 // the message accordingly. 1331 if (mSc.equals(SC_PUK) || mSc.equals(SC_PUK2)) { 1332 sb.append(mContext.getText( 1333 com.android.internal.R.string.badPuk)); 1334 } else { 1335 sb.append(mContext.getText( 1336 com.android.internal.R.string.badPin)); 1337 } 1338 // Get the No. of retries remaining to unlock PUK/PUK2 1339 int attemptsRemaining = msg.arg1; 1340 if (attemptsRemaining <= 0) { 1341 Rlog.d(LOG_TAG, "onSetComplete: PUK locked," 1342 + " cancel as lock screen will handle this"); 1343 mState = State.CANCELLED; 1344 } else if (attemptsRemaining > 0) { 1345 Rlog.d(LOG_TAG, "onSetComplete: attemptsRemaining="+attemptsRemaining); 1346 sb.append(mContext.getResources().getQuantityString( 1347 com.android.internal.R.plurals.pinpuk_attempts, 1348 attemptsRemaining, attemptsRemaining)); 1349 } 1350 } else { 1351 sb.append(mContext.getText( 1352 com.android.internal.R.string.passwordIncorrect)); 1353 } 1354 } else if (err == CommandException.Error.SIM_PUK2) { 1355 sb.append(mContext.getText( 1356 com.android.internal.R.string.badPin)); 1357 sb.append("\n"); 1358 sb.append(mContext.getText( 1359 com.android.internal.R.string.needPuk2)); 1360 } else if (err == CommandException.Error.REQUEST_NOT_SUPPORTED) { 1361 if (mSc.equals(SC_PIN)) { 1362 sb.append(mContext.getText(com.android.internal.R.string.enablePin)); 1363 } 1364 } else if (err == CommandException.Error.FDN_CHECK_FAILURE) { 1365 Rlog.i(LOG_TAG, "FDN_CHECK_FAILURE"); 1366 sb.append(mContext.getText(com.android.internal.R.string.mmiFdnError)); 1367 } else if (err == CommandException.Error.MODEM_ERR) { 1368 // Some carriers do not allow changing call forwarding settings while roaming 1369 // and will return an error from the modem. 1370 if (isServiceCodeCallForwarding(mSc) 1371 && mPhone.getServiceState().getVoiceRoaming() 1372 && !mPhone.supports3gppCallForwardingWhileRoaming()) { 1373 sb.append(mContext.getText( 1374 com.android.internal.R.string.mmiErrorWhileRoaming)); 1375 } else { 1376 sb.append(getErrorMessage(ar)); 1377 } 1378 } else { 1379 sb.append(getErrorMessage(ar)); 1380 } 1381 } else { 1382 sb.append(mContext.getText( 1383 com.android.internal.R.string.mmiError)); 1384 } 1385 } else if (isActivate()) { 1386 mState = State.COMPLETE; 1387 if (mIsCallFwdReg) { 1388 sb.append(mContext.getText( 1389 com.android.internal.R.string.serviceRegistered)); 1390 } else { 1391 sb.append(mContext.getText( 1392 com.android.internal.R.string.serviceEnabled)); 1393 } 1394 // Record CLIR setting 1395 if (mSc.equals(SC_CLIR)) { 1396 mPhone.saveClirSetting(CommandsInterface.CLIR_INVOCATION); 1397 } 1398 } else if (isDeactivate()) { 1399 mState = State.COMPLETE; 1400 sb.append(mContext.getText( 1401 com.android.internal.R.string.serviceDisabled)); 1402 // Record CLIR setting 1403 if (mSc.equals(SC_CLIR)) { 1404 mPhone.saveClirSetting(CommandsInterface.CLIR_SUPPRESSION); 1405 } 1406 } else if (isRegister()) { 1407 mState = State.COMPLETE; 1408 sb.append(mContext.getText( 1409 com.android.internal.R.string.serviceRegistered)); 1410 } else if (isErasure()) { 1411 mState = State.COMPLETE; 1412 sb.append(mContext.getText( 1413 com.android.internal.R.string.serviceErased)); 1414 } else { 1415 mState = State.FAILED; 1416 sb.append(mContext.getText( 1417 com.android.internal.R.string.mmiError)); 1418 } 1419 1420 mMessage = sb; 1421 Rlog.d(LOG_TAG, "onSetComplete mmi=" + this); 1422 mPhone.onMMIDone(this); 1423 } 1424 1425 private void onGetClirComplete(AsyncResult ar)1426 onGetClirComplete(AsyncResult ar) { 1427 StringBuilder sb = new StringBuilder(getScString()); 1428 sb.append("\n"); 1429 1430 if (ar.exception != null) { 1431 mState = State.FAILED; 1432 sb.append(getErrorMessage(ar)); 1433 } else { 1434 int clirArgs[]; 1435 1436 clirArgs = (int[])ar.result; 1437 1438 // the 'm' parameter from TS 27.007 7.7 1439 switch (clirArgs[1]) { 1440 case 0: // CLIR not provisioned 1441 sb.append(mContext.getText( 1442 com.android.internal.R.string.serviceNotProvisioned)); 1443 mState = State.COMPLETE; 1444 break; 1445 1446 case 1: // CLIR provisioned in permanent mode 1447 sb.append(mContext.getText( 1448 com.android.internal.R.string.CLIRPermanent)); 1449 mState = State.COMPLETE; 1450 break; 1451 1452 case 2: // unknown (e.g. no network, etc.) 1453 sb.append(mContext.getText( 1454 com.android.internal.R.string.mmiError)); 1455 mState = State.FAILED; 1456 break; 1457 1458 case 3: // CLIR temporary mode presentation restricted 1459 1460 // the 'n' parameter from TS 27.007 7.7 1461 switch (clirArgs[0]) { 1462 default: 1463 case 0: // Default 1464 sb.append(mContext.getText( 1465 com.android.internal.R.string.CLIRDefaultOnNextCallOn)); 1466 break; 1467 case 1: // CLIR invocation 1468 sb.append(mContext.getText( 1469 com.android.internal.R.string.CLIRDefaultOnNextCallOn)); 1470 break; 1471 case 2: // CLIR suppression 1472 sb.append(mContext.getText( 1473 com.android.internal.R.string.CLIRDefaultOnNextCallOff)); 1474 break; 1475 } 1476 mState = State.COMPLETE; 1477 break; 1478 1479 case 4: // CLIR temporary mode presentation allowed 1480 // the 'n' parameter from TS 27.007 7.7 1481 switch (clirArgs[0]) { 1482 default: 1483 case 0: // Default 1484 sb.append(mContext.getText( 1485 com.android.internal.R.string.CLIRDefaultOffNextCallOff)); 1486 break; 1487 case 1: // CLIR invocation 1488 sb.append(mContext.getText( 1489 com.android.internal.R.string.CLIRDefaultOffNextCallOn)); 1490 break; 1491 case 2: // CLIR suppression 1492 sb.append(mContext.getText( 1493 com.android.internal.R.string.CLIRDefaultOffNextCallOff)); 1494 break; 1495 } 1496 1497 mState = State.COMPLETE; 1498 break; 1499 } 1500 } 1501 1502 mMessage = sb; 1503 Rlog.d(LOG_TAG, "onGetClirComplete: mmi=" + this); 1504 mPhone.onMMIDone(this); 1505 } 1506 1507 /** 1508 * @param serviceClass 1 bit of the service class bit vectory 1509 * @return String to be used for call forward query MMI response text. 1510 * Returns null if unrecognized 1511 */ 1512 1513 private CharSequence serviceClassToCFString(int serviceClass)1514 serviceClassToCFString (int serviceClass) { 1515 switch (serviceClass) { 1516 case SERVICE_CLASS_VOICE: 1517 return mContext.getText(com.android.internal.R.string.serviceClassVoice); 1518 case SERVICE_CLASS_DATA: 1519 return mContext.getText(com.android.internal.R.string.serviceClassData); 1520 case SERVICE_CLASS_FAX: 1521 return mContext.getText(com.android.internal.R.string.serviceClassFAX); 1522 case SERVICE_CLASS_SMS: 1523 return mContext.getText(com.android.internal.R.string.serviceClassSMS); 1524 case SERVICE_CLASS_DATA_SYNC: 1525 return mContext.getText(com.android.internal.R.string.serviceClassDataSync); 1526 case SERVICE_CLASS_DATA_ASYNC: 1527 return mContext.getText(com.android.internal.R.string.serviceClassDataAsync); 1528 case SERVICE_CLASS_PACKET: 1529 return mContext.getText(com.android.internal.R.string.serviceClassPacket); 1530 case SERVICE_CLASS_PAD: 1531 return mContext.getText(com.android.internal.R.string.serviceClassPAD); 1532 default: 1533 return null; 1534 } 1535 } 1536 1537 1538 /** one CallForwardInfo + serviceClassMask -> one line of text */ 1539 private CharSequence makeCFQueryResultMessage(CallForwardInfo info, int serviceClassMask)1540 makeCFQueryResultMessage(CallForwardInfo info, int serviceClassMask) { 1541 CharSequence template; 1542 String sources[] = {"{0}", "{1}", "{2}"}; 1543 CharSequence destinations[] = new CharSequence[3]; 1544 boolean needTimeTemplate; 1545 1546 // CF_REASON_NO_REPLY also has a time value associated with 1547 // it. All others don't. 1548 1549 needTimeTemplate = 1550 (info.reason == CommandsInterface.CF_REASON_NO_REPLY); 1551 1552 if (info.status == 1) { 1553 if (needTimeTemplate) { 1554 template = mContext.getText( 1555 com.android.internal.R.string.cfTemplateForwardedTime); 1556 } else { 1557 template = mContext.getText( 1558 com.android.internal.R.string.cfTemplateForwarded); 1559 } 1560 } else if (info.status == 0 && isEmptyOrNull(info.number)) { 1561 template = mContext.getText( 1562 com.android.internal.R.string.cfTemplateNotForwarded); 1563 } else { /* (info.status == 0) && !isEmptyOrNull(info.number) */ 1564 // A call forward record that is not active but contains 1565 // a phone number is considered "registered" 1566 1567 if (needTimeTemplate) { 1568 template = mContext.getText( 1569 com.android.internal.R.string.cfTemplateRegisteredTime); 1570 } else { 1571 template = mContext.getText( 1572 com.android.internal.R.string.cfTemplateRegistered); 1573 } 1574 } 1575 1576 // In the template (from strings.xmls) 1577 // {0} is one of "bearerServiceCode*" 1578 // {1} is dialing number 1579 // {2} is time in seconds 1580 1581 destinations[0] = serviceClassToCFString(info.serviceClass & serviceClassMask); 1582 destinations[1] = formatLtr( 1583 PhoneNumberUtils.stringFromStringAndTOA(info.number, info.toa)); 1584 destinations[2] = Integer.toString(info.timeSeconds); 1585 1586 if (info.reason == CommandsInterface.CF_REASON_UNCONDITIONAL && 1587 (info.serviceClass & serviceClassMask) 1588 == CommandsInterface.SERVICE_CLASS_VOICE) { 1589 boolean cffEnabled = (info.status == 1); 1590 mPhone.setVoiceCallForwardingFlag(1, cffEnabled, info.number); 1591 } 1592 1593 return TextUtils.replace(template, sources, destinations); 1594 } 1595 1596 /** 1597 * Used to format a string that should be displayed as LTR even in RTL locales 1598 */ formatLtr(String str)1599 private String formatLtr(String str) { 1600 BidiFormatter fmt = BidiFormatter.getInstance(); 1601 return str == null ? str : fmt.unicodeWrap(str, TextDirectionHeuristics.LTR, true); 1602 } 1603 1604 private void onQueryCfComplete(AsyncResult ar)1605 onQueryCfComplete(AsyncResult ar) { 1606 StringBuilder sb = new StringBuilder(getScString()); 1607 sb.append("\n"); 1608 1609 if (ar.exception != null) { 1610 mState = State.FAILED; 1611 sb.append(getErrorMessage(ar)); 1612 } else { 1613 CallForwardInfo infos[]; 1614 1615 infos = (CallForwardInfo[]) ar.result; 1616 1617 if (infos == null || infos.length == 0) { 1618 // Assume the default is not active 1619 sb.append(mContext.getText(com.android.internal.R.string.serviceDisabled)); 1620 1621 // Set unconditional CFF in SIM to false 1622 mPhone.setVoiceCallForwardingFlag(1, false, null); 1623 } else { 1624 1625 SpannableStringBuilder tb = new SpannableStringBuilder(); 1626 1627 // Each bit in the service class gets its own result line 1628 // The service classes may be split up over multiple 1629 // CallForwardInfos. So, for each service class, find out 1630 // which CallForwardInfo represents it and then build 1631 // the response text based on that 1632 1633 for (int serviceClassMask = 1 1634 ; serviceClassMask <= SERVICE_CLASS_MAX 1635 ; serviceClassMask <<= 1 1636 ) { 1637 for (int i = 0, s = infos.length; i < s ; i++) { 1638 if ((serviceClassMask & infos[i].serviceClass) != 0) { 1639 tb.append(makeCFQueryResultMessage(infos[i], 1640 serviceClassMask)); 1641 tb.append("\n"); 1642 } 1643 } 1644 } 1645 sb.append(tb); 1646 } 1647 1648 mState = State.COMPLETE; 1649 } 1650 1651 mMessage = sb; 1652 Rlog.d(LOG_TAG, "onQueryCfComplete: mmi=" + this); 1653 mPhone.onMMIDone(this); 1654 1655 } 1656 1657 private void onQueryComplete(AsyncResult ar)1658 onQueryComplete(AsyncResult ar) { 1659 StringBuilder sb = new StringBuilder(getScString()); 1660 sb.append("\n"); 1661 1662 if (ar.exception != null) { 1663 mState = State.FAILED; 1664 sb.append(getErrorMessage(ar)); 1665 } else { 1666 int[] ints = (int[])ar.result; 1667 1668 if (ints.length != 0) { 1669 if (ints[0] == 0) { 1670 sb.append(mContext.getText(com.android.internal.R.string.serviceDisabled)); 1671 } else if (mSc.equals(SC_WAIT)) { 1672 // Call Waiting includes additional data in the response. 1673 sb.append(createQueryCallWaitingResultMessage(ints[1])); 1674 } else if (isServiceCodeCallBarring(mSc)) { 1675 // ints[0] for Call Barring is a bit vector of services 1676 sb.append(createQueryCallBarringResultMessage(ints[0])); 1677 } else if (ints[0] == 1) { 1678 // for all other services, treat it as a boolean 1679 sb.append(mContext.getText(com.android.internal.R.string.serviceEnabled)); 1680 } else { 1681 sb.append(mContext.getText(com.android.internal.R.string.mmiError)); 1682 } 1683 } else { 1684 sb.append(mContext.getText(com.android.internal.R.string.mmiError)); 1685 } 1686 mState = State.COMPLETE; 1687 } 1688 1689 mMessage = sb; 1690 Rlog.d(LOG_TAG, "onQueryComplete: mmi=" + this); 1691 mPhone.onMMIDone(this); 1692 } 1693 1694 private CharSequence createQueryCallWaitingResultMessage(int serviceClass)1695 createQueryCallWaitingResultMessage(int serviceClass) { 1696 StringBuilder sb = 1697 new StringBuilder(mContext.getText(com.android.internal.R.string.serviceEnabledFor)); 1698 1699 for (int classMask = 1 1700 ; classMask <= SERVICE_CLASS_MAX 1701 ; classMask <<= 1 1702 ) { 1703 if ((classMask & serviceClass) != 0) { 1704 sb.append("\n"); 1705 sb.append(serviceClassToCFString(classMask & serviceClass)); 1706 } 1707 } 1708 return sb; 1709 } 1710 private CharSequence createQueryCallBarringResultMessage(int serviceClass)1711 createQueryCallBarringResultMessage(int serviceClass) 1712 { 1713 StringBuilder sb = new StringBuilder(mContext.getText(com.android.internal.R.string.serviceEnabledFor)); 1714 1715 for (int classMask = 1 1716 ; classMask <= SERVICE_CLASS_MAX 1717 ; classMask <<= 1 1718 ) { 1719 if ((classMask & serviceClass) != 0) { 1720 sb.append("\n"); 1721 sb.append(serviceClassToCFString(classMask & serviceClass)); 1722 } 1723 } 1724 return sb; 1725 } 1726 getUssdCallbackReceiver()1727 public ResultReceiver getUssdCallbackReceiver() { 1728 return this.mCallbackReceiver; 1729 } 1730 1731 /*** 1732 * TODO: It would be nice to have a method here that can take in a dialstring and 1733 * figure out if there is an MMI code embedded within it. This code would replace 1734 * some of the string parsing functionality in the Phone App's 1735 * SpecialCharSequenceMgr class. 1736 */ 1737 1738 @Override toString()1739 public String toString() { 1740 StringBuilder sb = new StringBuilder("GsmMmiCode {"); 1741 1742 sb.append("State=" + getState()); 1743 if (mAction != null) sb.append(" action=" + mAction); 1744 if (mSc != null) sb.append(" sc=" + mSc); 1745 if (mSia != null) sb.append(" sia=" + Rlog.pii(LOG_TAG, mSia)); 1746 if (mSib != null) sb.append(" sib=" + Rlog.pii(LOG_TAG, mSib)); 1747 if (mSic != null) sb.append(" sic=" + Rlog.pii(LOG_TAG, mSic)); 1748 if (mPoundString != null) sb.append(" poundString=" + Rlog.pii(LOG_TAG, mPoundString)); 1749 if (mDialingNumber != null) { 1750 sb.append(" dialingNumber=" + Rlog.pii(LOG_TAG, mDialingNumber)); 1751 } 1752 if (mPwd != null) sb.append(" pwd=" + Rlog.pii(LOG_TAG, mPwd)); 1753 if (mCallbackReceiver != null) sb.append(" hasReceiver"); 1754 sb.append("}"); 1755 return sb.toString(); 1756 } 1757 } 1758