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