1 /* 2 * Copyright (C) 2007 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.cat; 18 19 import android.content.Context; 20 import android.content.Intent; 21 import android.content.pm.PackageManager; 22 import android.content.pm.ResolveInfo; 23 import android.content.res.Resources.NotFoundException; 24 import android.os.AsyncResult; 25 import android.os.Handler; 26 import android.os.HandlerThread; 27 import android.os.Message; 28 import android.os.SystemProperties; 29 import android.telephony.SubscriptionManager; 30 import android.telephony.TelephonyManager; 31 32 import com.android.internal.telephony.CommandsInterface; 33 import com.android.internal.telephony.PhoneConstants; 34 import com.android.internal.telephony.SubscriptionController; 35 import com.android.internal.telephony.uicc.IccFileHandler; 36 import com.android.internal.telephony.uicc.IccRecords; 37 import com.android.internal.telephony.uicc.IccUtils; 38 import com.android.internal.telephony.uicc.UiccCard; 39 import com.android.internal.telephony.uicc.UiccCardApplication; 40 import com.android.internal.telephony.uicc.IccCardStatus.CardState; 41 import com.android.internal.telephony.uicc.IccRefreshResponse; 42 import com.android.internal.telephony.uicc.UiccController; 43 44 import java.io.ByteArrayOutputStream; 45 import java.util.List; 46 import java.util.Locale; 47 48 import static com.android.internal.telephony.cat.CatCmdMessage. 49 SetupEventListConstants.IDLE_SCREEN_AVAILABLE_EVENT; 50 import static com.android.internal.telephony.cat.CatCmdMessage. 51 SetupEventListConstants.LANGUAGE_SELECTION_EVENT; 52 53 class RilMessage { 54 int mId; 55 Object mData; 56 ResultCode mResCode; 57 RilMessage(int msgId, String rawData)58 RilMessage(int msgId, String rawData) { 59 mId = msgId; 60 mData = rawData; 61 } 62 RilMessage(RilMessage other)63 RilMessage(RilMessage other) { 64 mId = other.mId; 65 mData = other.mData; 66 mResCode = other.mResCode; 67 } 68 } 69 70 /** 71 * Class that implements SIM Toolkit Telephony Service. Interacts with the RIL 72 * and application. 73 * 74 * {@hide} 75 */ 76 public class CatService extends Handler implements AppInterface { 77 private static final boolean DBG = false; 78 79 // Class members 80 private static IccRecords mIccRecords; 81 private static UiccCardApplication mUiccApplication; 82 83 // Service members. 84 // Protects singleton instance lazy initialization. 85 private static final Object sInstanceLock = new Object(); 86 private static CatService[] sInstance = null; 87 private CommandsInterface mCmdIf; 88 private Context mContext; 89 private CatCmdMessage mCurrntCmd = null; 90 private CatCmdMessage mMenuCmd = null; 91 92 private RilMessageDecoder mMsgDecoder = null; 93 private boolean mStkAppInstalled = false; 94 95 private UiccController mUiccController; 96 private CardState mCardState = CardState.CARDSTATE_ABSENT; 97 98 // Service constants. 99 protected static final int MSG_ID_SESSION_END = 1; 100 protected static final int MSG_ID_PROACTIVE_COMMAND = 2; 101 protected static final int MSG_ID_EVENT_NOTIFY = 3; 102 protected static final int MSG_ID_CALL_SETUP = 4; 103 static final int MSG_ID_REFRESH = 5; 104 static final int MSG_ID_RESPONSE = 6; 105 static final int MSG_ID_SIM_READY = 7; 106 107 protected static final int MSG_ID_ICC_CHANGED = 8; 108 protected static final int MSG_ID_ALPHA_NOTIFY = 9; 109 110 static final int MSG_ID_RIL_MSG_DECODED = 10; 111 112 // Events to signal SIM presence or absent in the device. 113 private static final int MSG_ID_ICC_RECORDS_LOADED = 20; 114 115 //Events to signal SIM REFRESH notificatations 116 private static final int MSG_ID_ICC_REFRESH = 30; 117 118 private static final int DEV_ID_KEYPAD = 0x01; 119 private static final int DEV_ID_DISPLAY = 0x02; 120 private static final int DEV_ID_UICC = 0x81; 121 private static final int DEV_ID_TERMINAL = 0x82; 122 private static final int DEV_ID_NETWORK = 0x83; 123 124 static final String STK_DEFAULT = "Default Message"; 125 126 private HandlerThread mHandlerThread; 127 private int mSlotId; 128 129 /* For multisim catservice should not be singleton */ CatService(CommandsInterface ci, UiccCardApplication ca, IccRecords ir, Context context, IccFileHandler fh, UiccCard ic, int slotId)130 private CatService(CommandsInterface ci, UiccCardApplication ca, IccRecords ir, 131 Context context, IccFileHandler fh, UiccCard ic, int slotId) { 132 if (ci == null || ca == null || ir == null || context == null || fh == null 133 || ic == null) { 134 throw new NullPointerException( 135 "Service: Input parameters must not be null"); 136 } 137 mCmdIf = ci; 138 mContext = context; 139 mSlotId = slotId; 140 mHandlerThread = new HandlerThread("Cat Telephony service" + slotId); 141 mHandlerThread.start(); 142 143 // Get the RilMessagesDecoder for decoding the messages. 144 mMsgDecoder = RilMessageDecoder.getInstance(this, fh, slotId); 145 if (null == mMsgDecoder) { 146 CatLog.d(this, "Null RilMessageDecoder instance"); 147 return; 148 } 149 mMsgDecoder.start(); 150 151 // Register ril events handling. 152 mCmdIf.setOnCatSessionEnd(this, MSG_ID_SESSION_END, null); 153 mCmdIf.setOnCatProactiveCmd(this, MSG_ID_PROACTIVE_COMMAND, null); 154 mCmdIf.setOnCatEvent(this, MSG_ID_EVENT_NOTIFY, null); 155 mCmdIf.setOnCatCallSetUp(this, MSG_ID_CALL_SETUP, null); 156 //mCmdIf.setOnSimRefresh(this, MSG_ID_REFRESH, null); 157 158 mCmdIf.registerForIccRefresh(this, MSG_ID_ICC_REFRESH, null); 159 mCmdIf.setOnCatCcAlphaNotify(this, MSG_ID_ALPHA_NOTIFY, null); 160 161 mIccRecords = ir; 162 mUiccApplication = ca; 163 164 // Register for SIM ready event. 165 mIccRecords.registerForRecordsLoaded(this, MSG_ID_ICC_RECORDS_LOADED, null); 166 CatLog.d(this, "registerForRecordsLoaded slotid=" + mSlotId + " instance:" + this); 167 168 169 mUiccController = UiccController.getInstance(); 170 mUiccController.registerForIccChanged(this, MSG_ID_ICC_CHANGED, null); 171 172 // Check if STK application is available 173 mStkAppInstalled = isStkAppInstalled(); 174 175 CatLog.d(this, "Running CAT service on Slotid: " + mSlotId + 176 ". STK app installed:" + mStkAppInstalled); 177 } 178 179 /** 180 * Used for instantiating the Service from the Card. 181 * 182 * @param ci CommandsInterface object 183 * @param context phone app context 184 * @param ic Icc card 185 * @param slotId to know the index of card 186 * @return The only Service object in the system 187 */ getInstance(CommandsInterface ci, Context context, UiccCard ic, int slotId)188 public static CatService getInstance(CommandsInterface ci, 189 Context context, UiccCard ic, int slotId) { 190 UiccCardApplication ca = null; 191 IccFileHandler fh = null; 192 IccRecords ir = null; 193 if (ic != null) { 194 /* Since Cat is not tied to any application, but rather is Uicc application 195 * in itself - just get first FileHandler and IccRecords object 196 */ 197 ca = ic.getApplicationIndex(0); 198 if (ca != null) { 199 fh = ca.getIccFileHandler(); 200 ir = ca.getIccRecords(); 201 } 202 } 203 204 synchronized (sInstanceLock) { 205 if (sInstance == null) { 206 int simCount = TelephonyManager.getDefault().getSimCount(); 207 sInstance = new CatService[simCount]; 208 for (int i = 0; i < simCount; i++) { 209 sInstance[i] = null; 210 } 211 } 212 if (sInstance[slotId] == null) { 213 if (ci == null || ca == null || ir == null || context == null || fh == null 214 || ic == null) { 215 return null; 216 } 217 218 sInstance[slotId] = new CatService(ci, ca, ir, context, fh, ic, slotId); 219 } else if ((ir != null) && (mIccRecords != ir)) { 220 if (mIccRecords != null) { 221 mIccRecords.unregisterForRecordsLoaded(sInstance[slotId]); 222 } 223 224 mIccRecords = ir; 225 mUiccApplication = ca; 226 227 mIccRecords.registerForRecordsLoaded(sInstance[slotId], 228 MSG_ID_ICC_RECORDS_LOADED, null); 229 CatLog.d(sInstance[slotId], "registerForRecordsLoaded slotid=" + slotId 230 + " instance:" + sInstance[slotId]); 231 } 232 return sInstance[slotId]; 233 } 234 } 235 dispose()236 public void dispose() { 237 synchronized (sInstanceLock) { 238 CatLog.d(this, "Disposing CatService object"); 239 mIccRecords.unregisterForRecordsLoaded(this); 240 241 // Clean up stk icon if dispose is called 242 broadcastCardStateAndIccRefreshResp(CardState.CARDSTATE_ABSENT, null); 243 244 mCmdIf.unSetOnCatSessionEnd(this); 245 mCmdIf.unSetOnCatProactiveCmd(this); 246 mCmdIf.unSetOnCatEvent(this); 247 mCmdIf.unSetOnCatCallSetUp(this); 248 mCmdIf.unSetOnCatCcAlphaNotify(this); 249 250 mCmdIf.unregisterForIccRefresh(this); 251 if (mUiccController != null) { 252 mUiccController.unregisterForIccChanged(this); 253 mUiccController = null; 254 } 255 mMsgDecoder.dispose(); 256 mMsgDecoder = null; 257 mHandlerThread.quit(); 258 mHandlerThread = null; 259 removeCallbacksAndMessages(null); 260 if (sInstance != null) { 261 if (SubscriptionManager.isValidSlotIndex(mSlotId)) { 262 sInstance[mSlotId] = null; 263 } else { 264 CatLog.d(this, "error: invaild slot id: " + mSlotId); 265 } 266 } 267 } 268 } 269 270 @Override finalize()271 protected void finalize() { 272 CatLog.d(this, "Service finalized"); 273 } 274 handleRilMsg(RilMessage rilMsg)275 private void handleRilMsg(RilMessage rilMsg) { 276 if (rilMsg == null) { 277 return; 278 } 279 280 // dispatch messages 281 CommandParams cmdParams = null; 282 switch (rilMsg.mId) { 283 case MSG_ID_EVENT_NOTIFY: 284 if (rilMsg.mResCode == ResultCode.OK) { 285 cmdParams = (CommandParams) rilMsg.mData; 286 if (cmdParams != null) { 287 handleCommand(cmdParams, false); 288 } 289 } 290 break; 291 case MSG_ID_PROACTIVE_COMMAND: 292 try { 293 cmdParams = (CommandParams) rilMsg.mData; 294 } catch (ClassCastException e) { 295 // for error handling : cast exception 296 CatLog.d(this, "Fail to parse proactive command"); 297 // Don't send Terminal Resp if command detail is not available 298 if (mCurrntCmd != null) { 299 sendTerminalResponse(mCurrntCmd.mCmdDet, ResultCode.CMD_DATA_NOT_UNDERSTOOD, 300 false, 0x00, null); 301 } 302 break; 303 } 304 if (cmdParams != null) { 305 if (rilMsg.mResCode == ResultCode.OK) { 306 handleCommand(cmdParams, true); 307 } else { 308 // for proactive commands that couldn't be decoded 309 // successfully respond with the code generated by the 310 // message decoder. 311 sendTerminalResponse(cmdParams.mCmdDet, rilMsg.mResCode, 312 false, 0, null); 313 } 314 } 315 break; 316 case MSG_ID_REFRESH: 317 cmdParams = (CommandParams) rilMsg.mData; 318 if (cmdParams != null) { 319 handleCommand(cmdParams, false); 320 } 321 break; 322 case MSG_ID_SESSION_END: 323 handleSessionEnd(); 324 break; 325 case MSG_ID_CALL_SETUP: 326 // prior event notify command supplied all the information 327 // needed for set up call processing. 328 break; 329 } 330 } 331 332 /** 333 * This function validates the events in SETUP_EVENT_LIST which are currently 334 * supported by the Android framework. In case of SETUP_EVENT_LIST has NULL events 335 * or no events, all the events need to be reset. 336 */ isSupportedSetupEventCommand(CatCmdMessage cmdMsg)337 private boolean isSupportedSetupEventCommand(CatCmdMessage cmdMsg) { 338 boolean flag = true; 339 340 for (int eventVal: cmdMsg.getSetEventList().eventList) { 341 CatLog.d(this,"Event: " + eventVal); 342 switch (eventVal) { 343 /* Currently android is supporting only the below events in SetupEventList 344 * Language Selection. */ 345 case IDLE_SCREEN_AVAILABLE_EVENT: 346 case LANGUAGE_SELECTION_EVENT: 347 break; 348 default: 349 flag = false; 350 } 351 } 352 return flag; 353 } 354 355 /** 356 * Handles RIL_UNSOL_STK_EVENT_NOTIFY or RIL_UNSOL_STK_PROACTIVE_COMMAND command 357 * from RIL. 358 * Sends valid proactive command data to the application using intents. 359 * RIL_REQUEST_STK_SEND_TERMINAL_RESPONSE will be send back if the command is 360 * from RIL_UNSOL_STK_PROACTIVE_COMMAND. 361 */ handleCommand(CommandParams cmdParams, boolean isProactiveCmd)362 private void handleCommand(CommandParams cmdParams, boolean isProactiveCmd) { 363 CatLog.d(this, cmdParams.getCommandType().name()); 364 365 // Log all proactive commands. 366 if (isProactiveCmd) { 367 if (mUiccController != null) { 368 mUiccController.addCardLog("ProactiveCommand mSlotId=" + mSlotId + 369 " cmdParams=" + cmdParams); 370 } 371 } 372 373 CharSequence message; 374 ResultCode resultCode; 375 CatCmdMessage cmdMsg = new CatCmdMessage(cmdParams); 376 switch (cmdParams.getCommandType()) { 377 case SET_UP_MENU: 378 if (removeMenu(cmdMsg.getMenu())) { 379 mMenuCmd = null; 380 } else { 381 mMenuCmd = cmdMsg; 382 } 383 resultCode = cmdParams.mLoadIconFailed ? ResultCode.PRFRMD_ICON_NOT_DISPLAYED 384 : ResultCode.OK; 385 sendTerminalResponse(cmdParams.mCmdDet, resultCode, false, 0, null); 386 break; 387 case DISPLAY_TEXT: 388 break; 389 case REFRESH: 390 // ME side only handles refresh commands which meant to remove IDLE 391 // MODE TEXT. 392 cmdParams.mCmdDet.typeOfCommand = CommandType.SET_UP_IDLE_MODE_TEXT.value(); 393 break; 394 case SET_UP_IDLE_MODE_TEXT: 395 resultCode = cmdParams.mLoadIconFailed ? ResultCode.PRFRMD_ICON_NOT_DISPLAYED 396 : ResultCode.OK; 397 sendTerminalResponse(cmdParams.mCmdDet,resultCode, false, 0, null); 398 break; 399 case SET_UP_EVENT_LIST: 400 if (isSupportedSetupEventCommand(cmdMsg)) { 401 sendTerminalResponse(cmdParams.mCmdDet, ResultCode.OK, false, 0, null); 402 } else { 403 sendTerminalResponse(cmdParams.mCmdDet, ResultCode.BEYOND_TERMINAL_CAPABILITY, 404 false, 0, null); 405 } 406 break; 407 case PROVIDE_LOCAL_INFORMATION: 408 ResponseData resp; 409 switch (cmdParams.mCmdDet.commandQualifier) { 410 case CommandParamsFactory.DTTZ_SETTING: 411 resp = new DTTZResponseData(null); 412 sendTerminalResponse(cmdParams.mCmdDet, ResultCode.OK, false, 0, resp); 413 break; 414 case CommandParamsFactory.LANGUAGE_SETTING: 415 resp = new LanguageResponseData(Locale.getDefault().getLanguage()); 416 sendTerminalResponse(cmdParams.mCmdDet, ResultCode.OK, false, 0, resp); 417 break; 418 default: 419 sendTerminalResponse(cmdParams.mCmdDet, ResultCode.OK, false, 0, null); 420 } 421 // No need to start STK app here. 422 return; 423 case LAUNCH_BROWSER: 424 if ((((LaunchBrowserParams) cmdParams).mConfirmMsg.text != null) 425 && (((LaunchBrowserParams) cmdParams).mConfirmMsg.text.equals(STK_DEFAULT))) { 426 message = mContext.getText(com.android.internal.R.string.launchBrowserDefault); 427 ((LaunchBrowserParams) cmdParams).mConfirmMsg.text = message.toString(); 428 } 429 break; 430 case SELECT_ITEM: 431 case GET_INPUT: 432 case GET_INKEY: 433 break; 434 case SEND_DTMF: 435 case SEND_SMS: 436 case SEND_SS: 437 case SEND_USSD: 438 if ((((DisplayTextParams)cmdParams).mTextMsg.text != null) 439 && (((DisplayTextParams)cmdParams).mTextMsg.text.equals(STK_DEFAULT))) { 440 message = mContext.getText(com.android.internal.R.string.sending); 441 ((DisplayTextParams)cmdParams).mTextMsg.text = message.toString(); 442 } 443 break; 444 case PLAY_TONE: 445 break; 446 case SET_UP_CALL: 447 if ((((CallSetupParams) cmdParams).mConfirmMsg.text != null) 448 && (((CallSetupParams) cmdParams).mConfirmMsg.text.equals(STK_DEFAULT))) { 449 message = mContext.getText(com.android.internal.R.string.SetupCallDefault); 450 ((CallSetupParams) cmdParams).mConfirmMsg.text = message.toString(); 451 } 452 break; 453 case OPEN_CHANNEL: 454 case CLOSE_CHANNEL: 455 case RECEIVE_DATA: 456 case SEND_DATA: 457 BIPClientParams cmd = (BIPClientParams) cmdParams; 458 /* Per 3GPP specification 102.223, 459 * if the alpha identifier is not provided by the UICC, 460 * the terminal MAY give information to the user 461 * noAlphaUsrCnf defines if you need to show user confirmation or not 462 */ 463 boolean noAlphaUsrCnf = false; 464 try { 465 noAlphaUsrCnf = mContext.getResources().getBoolean( 466 com.android.internal.R.bool.config_stkNoAlphaUsrCnf); 467 } catch (NotFoundException e) { 468 noAlphaUsrCnf = false; 469 } 470 if ((cmd.mTextMsg.text == null) && (cmd.mHasAlphaId || noAlphaUsrCnf)) { 471 CatLog.d(this, "cmd " + cmdParams.getCommandType() + " with null alpha id"); 472 // If alpha length is zero, we just respond with OK. 473 if (isProactiveCmd) { 474 sendTerminalResponse(cmdParams.mCmdDet, ResultCode.OK, false, 0, null); 475 } else if (cmdParams.getCommandType() == CommandType.OPEN_CHANNEL) { 476 mCmdIf.handleCallSetupRequestFromSim(true, null); 477 } 478 return; 479 } 480 // Respond with permanent failure to avoid retry if STK app is not present. 481 if (!mStkAppInstalled) { 482 CatLog.d(this, "No STK application found."); 483 if (isProactiveCmd) { 484 sendTerminalResponse(cmdParams.mCmdDet, 485 ResultCode.BEYOND_TERMINAL_CAPABILITY, 486 false, 0, null); 487 return; 488 } 489 } 490 /* 491 * CLOSE_CHANNEL, RECEIVE_DATA and SEND_DATA can be delivered by 492 * either PROACTIVE_COMMAND or EVENT_NOTIFY. 493 * If PROACTIVE_COMMAND is used for those commands, send terminal 494 * response here. 495 */ 496 if (isProactiveCmd && 497 ((cmdParams.getCommandType() == CommandType.CLOSE_CHANNEL) || 498 (cmdParams.getCommandType() == CommandType.RECEIVE_DATA) || 499 (cmdParams.getCommandType() == CommandType.SEND_DATA))) { 500 sendTerminalResponse(cmdParams.mCmdDet, ResultCode.OK, false, 0, null); 501 } 502 break; 503 default: 504 CatLog.d(this, "Unsupported command"); 505 return; 506 } 507 mCurrntCmd = cmdMsg; 508 broadcastCatCmdIntent(cmdMsg); 509 } 510 511 broadcastCatCmdIntent(CatCmdMessage cmdMsg)512 private void broadcastCatCmdIntent(CatCmdMessage cmdMsg) { 513 Intent intent = new Intent(AppInterface.CAT_CMD_ACTION); 514 intent.putExtra("STK CMD", cmdMsg); 515 intent.putExtra("SLOT_ID", mSlotId); 516 intent.setComponent(AppInterface.getDefaultSTKApplication()); 517 CatLog.d(this, "Sending CmdMsg: " + cmdMsg+ " on slotid:" + mSlotId); 518 mContext.sendBroadcast(intent, AppInterface.STK_PERMISSION); 519 } 520 521 /** 522 * Handles RIL_UNSOL_STK_SESSION_END unsolicited command from RIL. 523 * 524 */ handleSessionEnd()525 private void handleSessionEnd() { 526 CatLog.d(this, "SESSION END on "+ mSlotId); 527 528 mCurrntCmd = mMenuCmd; 529 Intent intent = new Intent(AppInterface.CAT_SESSION_END_ACTION); 530 intent.putExtra("SLOT_ID", mSlotId); 531 intent.setComponent(AppInterface.getDefaultSTKApplication()); 532 mContext.sendBroadcast(intent, AppInterface.STK_PERMISSION); 533 } 534 535 sendTerminalResponse(CommandDetails cmdDet, ResultCode resultCode, boolean includeAdditionalInfo, int additionalInfo, ResponseData resp)536 private void sendTerminalResponse(CommandDetails cmdDet, 537 ResultCode resultCode, boolean includeAdditionalInfo, 538 int additionalInfo, ResponseData resp) { 539 540 if (cmdDet == null) { 541 return; 542 } 543 ByteArrayOutputStream buf = new ByteArrayOutputStream(); 544 545 Input cmdInput = null; 546 if (mCurrntCmd != null) { 547 cmdInput = mCurrntCmd.geInput(); 548 } 549 550 // command details 551 int tag = ComprehensionTlvTag.COMMAND_DETAILS.value(); 552 if (cmdDet.compRequired) { 553 tag |= 0x80; 554 } 555 buf.write(tag); 556 buf.write(0x03); // length 557 buf.write(cmdDet.commandNumber); 558 buf.write(cmdDet.typeOfCommand); 559 buf.write(cmdDet.commandQualifier); 560 561 // device identities 562 // According to TS102.223/TS31.111 section 6.8 Structure of 563 // TERMINAL RESPONSE, "For all SIMPLE-TLV objects with Min=N, 564 // the ME should set the CR(comprehension required) flag to 565 // comprehension not required.(CR=0)" 566 // Since DEVICE_IDENTITIES and DURATION TLVs have Min=N, 567 // the CR flag is not set. 568 tag = ComprehensionTlvTag.DEVICE_IDENTITIES.value(); 569 buf.write(tag); 570 buf.write(0x02); // length 571 buf.write(DEV_ID_TERMINAL); // source device id 572 buf.write(DEV_ID_UICC); // destination device id 573 574 // result 575 tag = ComprehensionTlvTag.RESULT.value(); 576 if (cmdDet.compRequired) { 577 tag |= 0x80; 578 } 579 buf.write(tag); 580 int length = includeAdditionalInfo ? 2 : 1; 581 buf.write(length); 582 buf.write(resultCode.value()); 583 584 // additional info 585 if (includeAdditionalInfo) { 586 buf.write(additionalInfo); 587 } 588 589 // Fill optional data for each corresponding command 590 if (resp != null) { 591 resp.format(buf); 592 } else { 593 encodeOptionalTags(cmdDet, resultCode, cmdInput, buf); 594 } 595 596 byte[] rawData = buf.toByteArray(); 597 String hexString = IccUtils.bytesToHexString(rawData); 598 if (DBG) { 599 CatLog.d(this, "TERMINAL RESPONSE: " + hexString); 600 } 601 602 mCmdIf.sendTerminalResponse(hexString, null); 603 } 604 encodeOptionalTags(CommandDetails cmdDet, ResultCode resultCode, Input cmdInput, ByteArrayOutputStream buf)605 private void encodeOptionalTags(CommandDetails cmdDet, 606 ResultCode resultCode, Input cmdInput, ByteArrayOutputStream buf) { 607 CommandType cmdType = AppInterface.CommandType.fromInt(cmdDet.typeOfCommand); 608 if (cmdType != null) { 609 switch (cmdType) { 610 case GET_INKEY: 611 // ETSI TS 102 384,27.22.4.2.8.4.2. 612 // If it is a response for GET_INKEY command and the response timeout 613 // occured, then add DURATION TLV for variable timeout case. 614 if ((resultCode.value() == ResultCode.NO_RESPONSE_FROM_USER.value()) && 615 (cmdInput != null) && (cmdInput.duration != null)) { 616 getInKeyResponse(buf, cmdInput); 617 } 618 break; 619 case PROVIDE_LOCAL_INFORMATION: 620 if ((cmdDet.commandQualifier == CommandParamsFactory.LANGUAGE_SETTING) && 621 (resultCode.value() == ResultCode.OK.value())) { 622 getPliResponse(buf); 623 } 624 break; 625 default: 626 CatLog.d(this, "encodeOptionalTags() Unsupported Cmd details=" + cmdDet); 627 break; 628 } 629 } else { 630 CatLog.d(this, "encodeOptionalTags() bad Cmd details=" + cmdDet); 631 } 632 } 633 getInKeyResponse(ByteArrayOutputStream buf, Input cmdInput)634 private void getInKeyResponse(ByteArrayOutputStream buf, Input cmdInput) { 635 int tag = ComprehensionTlvTag.DURATION.value(); 636 637 buf.write(tag); 638 buf.write(0x02); // length 639 buf.write(cmdInput.duration.timeUnit.SECOND.value()); // Time (Unit,Seconds) 640 buf.write(cmdInput.duration.timeInterval); // Time Duration 641 } 642 getPliResponse(ByteArrayOutputStream buf)643 private void getPliResponse(ByteArrayOutputStream buf) { 644 // Locale Language Setting 645 final String lang = Locale.getDefault().getLanguage(); 646 647 if (lang != null) { 648 // tag 649 int tag = ComprehensionTlvTag.LANGUAGE.value(); 650 buf.write(tag); 651 ResponseData.writeLength(buf, lang.length()); 652 buf.write(lang.getBytes(), 0, lang.length()); 653 } 654 } 655 sendMenuSelection(int menuId, boolean helpRequired)656 private void sendMenuSelection(int menuId, boolean helpRequired) { 657 658 ByteArrayOutputStream buf = new ByteArrayOutputStream(); 659 660 // tag 661 int tag = BerTlv.BER_MENU_SELECTION_TAG; 662 buf.write(tag); 663 664 // length 665 buf.write(0x00); // place holder 666 667 // device identities 668 tag = 0x80 | ComprehensionTlvTag.DEVICE_IDENTITIES.value(); 669 buf.write(tag); 670 buf.write(0x02); // length 671 buf.write(DEV_ID_KEYPAD); // source device id 672 buf.write(DEV_ID_UICC); // destination device id 673 674 // item identifier 675 tag = 0x80 | ComprehensionTlvTag.ITEM_ID.value(); 676 buf.write(tag); 677 buf.write(0x01); // length 678 buf.write(menuId); // menu identifier chosen 679 680 // help request 681 if (helpRequired) { 682 tag = ComprehensionTlvTag.HELP_REQUEST.value(); 683 buf.write(tag); 684 buf.write(0x00); // length 685 } 686 687 byte[] rawData = buf.toByteArray(); 688 689 // write real length 690 int len = rawData.length - 2; // minus (tag + length) 691 rawData[1] = (byte) len; 692 693 String hexString = IccUtils.bytesToHexString(rawData); 694 695 mCmdIf.sendEnvelope(hexString, null); 696 } 697 eventDownload(int event, int sourceId, int destinationId, byte[] additionalInfo, boolean oneShot)698 private void eventDownload(int event, int sourceId, int destinationId, 699 byte[] additionalInfo, boolean oneShot) { 700 701 ByteArrayOutputStream buf = new ByteArrayOutputStream(); 702 703 // tag 704 int tag = BerTlv.BER_EVENT_DOWNLOAD_TAG; 705 buf.write(tag); 706 707 // length 708 buf.write(0x00); // place holder, assume length < 128. 709 710 // event list 711 tag = 0x80 | ComprehensionTlvTag.EVENT_LIST.value(); 712 buf.write(tag); 713 buf.write(0x01); // length 714 buf.write(event); // event value 715 716 // device identities 717 tag = 0x80 | ComprehensionTlvTag.DEVICE_IDENTITIES.value(); 718 buf.write(tag); 719 buf.write(0x02); // length 720 buf.write(sourceId); // source device id 721 buf.write(destinationId); // destination device id 722 723 /* 724 * Check for type of event download to be sent to UICC - Browser 725 * termination,Idle screen available, User activity, Language selection 726 * etc as mentioned under ETSI TS 102 223 section 7.5 727 */ 728 729 /* 730 * Currently the below events are supported: 731 * Language Selection Event. 732 * Other event download commands should be encoded similar way 733 */ 734 /* TODO: eventDownload should be extended for other Envelope Commands */ 735 switch (event) { 736 case IDLE_SCREEN_AVAILABLE_EVENT: 737 CatLog.d(sInstance, " Sending Idle Screen Available event download to ICC"); 738 break; 739 case LANGUAGE_SELECTION_EVENT: 740 CatLog.d(sInstance, " Sending Language Selection event download to ICC"); 741 tag = 0x80 | ComprehensionTlvTag.LANGUAGE.value(); 742 buf.write(tag); 743 // Language length should be 2 byte 744 buf.write(0x02); 745 break; 746 default: 747 break; 748 } 749 750 // additional information 751 if (additionalInfo != null) { 752 for (byte b : additionalInfo) { 753 buf.write(b); 754 } 755 } 756 757 byte[] rawData = buf.toByteArray(); 758 759 // write real length 760 int len = rawData.length - 2; // minus (tag + length) 761 rawData[1] = (byte) len; 762 763 String hexString = IccUtils.bytesToHexString(rawData); 764 765 CatLog.d(this, "ENVELOPE COMMAND: " + hexString); 766 767 mCmdIf.sendEnvelope(hexString, null); 768 } 769 770 /** 771 * Used by application to get an AppInterface object. 772 * 773 * @return The only Service object in the system 774 */ 775 //TODO Need to take care for MSIM getInstance()776 public static AppInterface getInstance() { 777 int slotId = PhoneConstants.DEFAULT_CARD_INDEX; 778 SubscriptionController sControl = SubscriptionController.getInstance(); 779 if (sControl != null) { 780 slotId = sControl.getSlotIndex(sControl.getDefaultSubId()); 781 } 782 return getInstance(null, null, null, slotId); 783 } 784 785 /** 786 * Used by application to get an AppInterface object. 787 * 788 * @return The only Service object in the system 789 */ getInstance(int slotId)790 public static AppInterface getInstance(int slotId) { 791 return getInstance(null, null, null, slotId); 792 } 793 794 @Override handleMessage(Message msg)795 public void handleMessage(Message msg) { 796 CatLog.d(this, "handleMessage[" + msg.what + "]"); 797 798 switch (msg.what) { 799 case MSG_ID_SESSION_END: 800 case MSG_ID_PROACTIVE_COMMAND: 801 case MSG_ID_EVENT_NOTIFY: 802 case MSG_ID_REFRESH: 803 CatLog.d(this, "ril message arrived,slotid:" + mSlotId); 804 String data = null; 805 if (msg.obj != null) { 806 AsyncResult ar = (AsyncResult) msg.obj; 807 if (ar != null && ar.result != null) { 808 try { 809 data = (String) ar.result; 810 } catch (ClassCastException e) { 811 break; 812 } 813 } 814 } 815 mMsgDecoder.sendStartDecodingMessageParams(new RilMessage(msg.what, data)); 816 break; 817 case MSG_ID_CALL_SETUP: 818 mMsgDecoder.sendStartDecodingMessageParams(new RilMessage(msg.what, null)); 819 break; 820 case MSG_ID_ICC_RECORDS_LOADED: 821 break; 822 case MSG_ID_RIL_MSG_DECODED: 823 handleRilMsg((RilMessage) msg.obj); 824 break; 825 case MSG_ID_RESPONSE: 826 handleCmdResponse((CatResponseMessage) msg.obj); 827 break; 828 case MSG_ID_ICC_CHANGED: 829 CatLog.d(this, "MSG_ID_ICC_CHANGED"); 830 updateIccAvailability(); 831 break; 832 case MSG_ID_ICC_REFRESH: 833 if (msg.obj != null) { 834 AsyncResult ar = (AsyncResult) msg.obj; 835 if (ar != null && ar.result != null) { 836 broadcastCardStateAndIccRefreshResp(CardState.CARDSTATE_PRESENT, 837 (IccRefreshResponse) ar.result); 838 } else { 839 CatLog.d(this,"Icc REFRESH with exception: " + ar.exception); 840 } 841 } else { 842 CatLog.d(this, "IccRefresh Message is null"); 843 } 844 break; 845 case MSG_ID_ALPHA_NOTIFY: 846 CatLog.d(this, "Received CAT CC Alpha message from card"); 847 if (msg.obj != null) { 848 AsyncResult ar = (AsyncResult) msg.obj; 849 if (ar != null && ar.result != null) { 850 broadcastAlphaMessage((String)ar.result); 851 } else { 852 CatLog.d(this, "CAT Alpha message: ar.result is null"); 853 } 854 } else { 855 CatLog.d(this, "CAT Alpha message: msg.obj is null"); 856 } 857 break; 858 default: 859 throw new AssertionError("Unrecognized CAT command: " + msg.what); 860 } 861 } 862 863 /** 864 ** This function sends a CARD status (ABSENT, PRESENT, REFRESH) to STK_APP. 865 ** This is triggered during ICC_REFRESH or CARD STATE changes. In case 866 ** REFRESH, additional information is sent in 'refresh_result' 867 ** 868 **/ broadcastCardStateAndIccRefreshResp(CardState cardState, IccRefreshResponse iccRefreshState)869 private void broadcastCardStateAndIccRefreshResp(CardState cardState, 870 IccRefreshResponse iccRefreshState) { 871 Intent intent = new Intent(AppInterface.CAT_ICC_STATUS_CHANGE); 872 boolean cardPresent = (cardState == CardState.CARDSTATE_PRESENT); 873 874 if (iccRefreshState != null) { 875 //This case is when MSG_ID_ICC_REFRESH is received. 876 intent.putExtra(AppInterface.REFRESH_RESULT, iccRefreshState.refreshResult); 877 CatLog.d(this, "Sending IccResult with Result: " 878 + iccRefreshState.refreshResult); 879 } 880 881 // This sends an intent with CARD_ABSENT (0 - false) /CARD_PRESENT (1 - true). 882 intent.putExtra(AppInterface.CARD_STATUS, cardPresent); 883 intent.setComponent(AppInterface.getDefaultSTKApplication()); 884 CatLog.d(this, "Sending Card Status: " 885 + cardState + " " + "cardPresent: " + cardPresent); 886 mContext.sendBroadcast(intent, AppInterface.STK_PERMISSION); 887 } 888 broadcastAlphaMessage(String alphaString)889 private void broadcastAlphaMessage(String alphaString) { 890 CatLog.d(this, "Broadcasting CAT Alpha message from card: " + alphaString); 891 Intent intent = new Intent(AppInterface.CAT_ALPHA_NOTIFY_ACTION); 892 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 893 intent.putExtra(AppInterface.ALPHA_STRING, alphaString); 894 intent.putExtra("SLOT_ID", mSlotId); 895 intent.setComponent(AppInterface.getDefaultSTKApplication()); 896 mContext.sendBroadcast(intent, AppInterface.STK_PERMISSION); 897 } 898 899 @Override onCmdResponse(CatResponseMessage resMsg)900 public synchronized void onCmdResponse(CatResponseMessage resMsg) { 901 if (resMsg == null) { 902 return; 903 } 904 // queue a response message. 905 Message msg = obtainMessage(MSG_ID_RESPONSE, resMsg); 906 msg.sendToTarget(); 907 } 908 validateResponse(CatResponseMessage resMsg)909 private boolean validateResponse(CatResponseMessage resMsg) { 910 boolean validResponse = false; 911 if ((resMsg.mCmdDet.typeOfCommand == CommandType.SET_UP_EVENT_LIST.value()) 912 || (resMsg.mCmdDet.typeOfCommand == CommandType.SET_UP_MENU.value())) { 913 CatLog.d(this, "CmdType: " + resMsg.mCmdDet.typeOfCommand); 914 validResponse = true; 915 } else if (mCurrntCmd != null) { 916 validResponse = resMsg.mCmdDet.compareTo(mCurrntCmd.mCmdDet); 917 CatLog.d(this, "isResponse for last valid cmd: " + validResponse); 918 } 919 return validResponse; 920 } 921 removeMenu(Menu menu)922 private boolean removeMenu(Menu menu) { 923 try { 924 if (menu.items.size() == 1 && menu.items.get(0) == null) { 925 return true; 926 } 927 } catch (NullPointerException e) { 928 CatLog.d(this, "Unable to get Menu's items size"); 929 return true; 930 } 931 return false; 932 } 933 handleCmdResponse(CatResponseMessage resMsg)934 private void handleCmdResponse(CatResponseMessage resMsg) { 935 // Make sure the response details match the last valid command. An invalid 936 // response is a one that doesn't have a corresponding proactive command 937 // and sending it can "confuse" the baseband/ril. 938 // One reason for out of order responses can be UI glitches. For example, 939 // if the application launch an activity, and that activity is stored 940 // by the framework inside the history stack. That activity will be 941 // available for relaunch using the latest application dialog 942 // (long press on the home button). Relaunching that activity can send 943 // the same command's result again to the CatService and can cause it to 944 // get out of sync with the SIM. This can happen in case of 945 // non-interactive type Setup Event List and SETUP_MENU proactive commands. 946 // Stk framework would have already sent Terminal Response to Setup Event 947 // List and SETUP_MENU proactive commands. After sometime Stk app will send 948 // Envelope Command/Event Download. In which case, the response details doesn't 949 // match with last valid command (which are not related). 950 // However, we should allow Stk framework to send the message to ICC. 951 if (!validateResponse(resMsg)) { 952 return; 953 } 954 ResponseData resp = null; 955 boolean helpRequired = false; 956 CommandDetails cmdDet = resMsg.getCmdDetails(); 957 AppInterface.CommandType type = AppInterface.CommandType.fromInt(cmdDet.typeOfCommand); 958 959 switch (resMsg.mResCode) { 960 case HELP_INFO_REQUIRED: 961 helpRequired = true; 962 // fall through 963 case OK: 964 case PRFRMD_WITH_PARTIAL_COMPREHENSION: 965 case PRFRMD_WITH_MISSING_INFO: 966 case PRFRMD_WITH_ADDITIONAL_EFS_READ: 967 case PRFRMD_ICON_NOT_DISPLAYED: 968 case PRFRMD_MODIFIED_BY_NAA: 969 case PRFRMD_LIMITED_SERVICE: 970 case PRFRMD_WITH_MODIFICATION: 971 case PRFRMD_NAA_NOT_ACTIVE: 972 case PRFRMD_TONE_NOT_PLAYED: 973 case LAUNCH_BROWSER_ERROR: 974 case TERMINAL_CRNTLY_UNABLE_TO_PROCESS: 975 switch (type) { 976 case SET_UP_MENU: 977 helpRequired = resMsg.mResCode == ResultCode.HELP_INFO_REQUIRED; 978 sendMenuSelection(resMsg.mUsersMenuSelection, helpRequired); 979 return; 980 case SELECT_ITEM: 981 resp = new SelectItemResponseData(resMsg.mUsersMenuSelection); 982 break; 983 case GET_INPUT: 984 case GET_INKEY: 985 Input input = mCurrntCmd.geInput(); 986 if (!input.yesNo) { 987 // when help is requested there is no need to send the text 988 // string object. 989 if (!helpRequired) { 990 resp = new GetInkeyInputResponseData(resMsg.mUsersInput, 991 input.ucs2, input.packed); 992 } 993 } else { 994 resp = new GetInkeyInputResponseData( 995 resMsg.mUsersYesNoSelection); 996 } 997 break; 998 case DISPLAY_TEXT: 999 if (resMsg.mResCode == ResultCode.TERMINAL_CRNTLY_UNABLE_TO_PROCESS) { 1000 // For screenbusy case there will be addtional information in the terminal 1001 // response. And the value of the additional information byte is 0x01. 1002 resMsg.setAdditionalInfo(0x01); 1003 } else { 1004 resMsg.mIncludeAdditionalInfo = false; 1005 resMsg.mAdditionalInfo = 0; 1006 } 1007 break; 1008 case LAUNCH_BROWSER: 1009 break; 1010 // 3GPP TS.102.223: Open Channel alpha confirmation should not send TR 1011 case OPEN_CHANNEL: 1012 case SET_UP_CALL: 1013 mCmdIf.handleCallSetupRequestFromSim(resMsg.mUsersConfirm, null); 1014 // No need to send terminal response for SET UP CALL. The user's 1015 // confirmation result is send back using a dedicated ril message 1016 // invoked by the CommandInterface call above. 1017 mCurrntCmd = null; 1018 return; 1019 case SET_UP_EVENT_LIST: 1020 if (IDLE_SCREEN_AVAILABLE_EVENT == resMsg.mEventValue) { 1021 eventDownload(resMsg.mEventValue, DEV_ID_DISPLAY, DEV_ID_UICC, 1022 resMsg.mAddedInfo, false); 1023 } else { 1024 eventDownload(resMsg.mEventValue, DEV_ID_TERMINAL, DEV_ID_UICC, 1025 resMsg.mAddedInfo, false); 1026 } 1027 // No need to send the terminal response after event download. 1028 return; 1029 default: 1030 break; 1031 } 1032 break; 1033 case BACKWARD_MOVE_BY_USER: 1034 case USER_NOT_ACCEPT: 1035 // if the user dismissed the alert dialog for a 1036 // setup call/open channel, consider that as the user 1037 // rejecting the call. Use dedicated API for this, rather than 1038 // sending a terminal response. 1039 if (type == CommandType.SET_UP_CALL || type == CommandType.OPEN_CHANNEL) { 1040 mCmdIf.handleCallSetupRequestFromSim(false, null); 1041 mCurrntCmd = null; 1042 return; 1043 } else { 1044 resp = null; 1045 } 1046 break; 1047 case NO_RESPONSE_FROM_USER: 1048 case UICC_SESSION_TERM_BY_USER: 1049 resp = null; 1050 break; 1051 default: 1052 return; 1053 } 1054 sendTerminalResponse(cmdDet, resMsg.mResCode, resMsg.mIncludeAdditionalInfo, 1055 resMsg.mAdditionalInfo, resp); 1056 mCurrntCmd = null; 1057 } 1058 isStkAppInstalled()1059 private boolean isStkAppInstalled() { 1060 Intent intent = new Intent(AppInterface.CAT_CMD_ACTION); 1061 PackageManager pm = mContext.getPackageManager(); 1062 List<ResolveInfo> broadcastReceivers = 1063 pm.queryBroadcastReceivers(intent, PackageManager.GET_META_DATA); 1064 int numReceiver = broadcastReceivers == null ? 0 : broadcastReceivers.size(); 1065 1066 return (numReceiver > 0); 1067 } 1068 update(CommandsInterface ci, Context context, UiccCard ic)1069 public void update(CommandsInterface ci, 1070 Context context, UiccCard ic) { 1071 UiccCardApplication ca = null; 1072 IccRecords ir = null; 1073 1074 if (ic != null) { 1075 /* Since Cat is not tied to any application, but rather is Uicc application 1076 * in itself - just get first FileHandler and IccRecords object 1077 */ 1078 ca = ic.getApplicationIndex(0); 1079 if (ca != null) { 1080 ir = ca.getIccRecords(); 1081 } 1082 } 1083 1084 synchronized (sInstanceLock) { 1085 if ((ir != null) && (mIccRecords != ir)) { 1086 if (mIccRecords != null) { 1087 mIccRecords.unregisterForRecordsLoaded(this); 1088 } 1089 1090 CatLog.d(this, 1091 "Reinitialize the Service with SIMRecords and UiccCardApplication"); 1092 mIccRecords = ir; 1093 mUiccApplication = ca; 1094 1095 // re-Register for SIM ready event. 1096 mIccRecords.registerForRecordsLoaded(this, MSG_ID_ICC_RECORDS_LOADED, null); 1097 CatLog.d(this, "registerForRecordsLoaded slotid=" + mSlotId + " instance:" + this); 1098 } 1099 } 1100 } 1101 updateIccAvailability()1102 void updateIccAvailability() { 1103 if (null == mUiccController) { 1104 return; 1105 } 1106 1107 CardState newState = CardState.CARDSTATE_ABSENT; 1108 UiccCard newCard = mUiccController.getUiccCard(mSlotId); 1109 if (newCard != null) { 1110 newState = newCard.getCardState(); 1111 } 1112 CardState oldState = mCardState; 1113 mCardState = newState; 1114 CatLog.d(this,"New Card State = " + newState + " " + "Old Card State = " + oldState); 1115 if (oldState == CardState.CARDSTATE_PRESENT && 1116 newState != CardState.CARDSTATE_PRESENT) { 1117 broadcastCardStateAndIccRefreshResp(newState, null); 1118 } else if (oldState != CardState.CARDSTATE_PRESENT && 1119 newState == CardState.CARDSTATE_PRESENT) { 1120 // Card moved to PRESENT STATE. 1121 mCmdIf.reportStkServiceIsRunning(null); 1122 } 1123 } 1124 } 1125