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 17package com.android.internal.telephony.gsm.stk; 18 19import android.content.Context; 20import android.content.Intent; 21import android.os.AsyncResult; 22import android.os.Handler; 23import android.os.HandlerThread; 24import android.os.Message; 25 26import com.android.internal.telephony.IccUtils; 27import com.android.internal.telephony.CommandsInterface; 28import com.android.internal.telephony.gsm.SimCard; 29import com.android.internal.telephony.gsm.SIMFileHandler; 30import com.android.internal.telephony.gsm.SIMRecords; 31 32import android.util.Config; 33 34import java.io.ByteArrayOutputStream; 35 36/** 37 * Enumeration for representing the tag value of COMPREHENSION-TLV objects. If 38 * you want to get the actual value, call {@link #value() value} method. 39 * 40 * {@hide} 41 */ 42enum ComprehensionTlvTag { 43 COMMAND_DETAILS(0x01), 44 DEVICE_IDENTITIES(0x02), 45 RESULT(0x03), 46 DURATION(0x04), 47 ALPHA_ID(0x05), 48 USSD_STRING(0x0a), 49 TEXT_STRING(0x0d), 50 TONE(0x0e), 51 ITEM(0x0f), 52 ITEM_ID(0x10), 53 RESPONSE_LENGTH(0x11), 54 FILE_LIST(0x12), 55 HELP_REQUEST(0x15), 56 DEFAULT_TEXT(0x17), 57 EVENT_LIST(0x19), 58 ICON_ID(0x1e), 59 ITEM_ICON_ID_LIST(0x1f), 60 IMMEDIATE_RESPONSE(0x2b), 61 LANGUAGE(0x2d), 62 URL(0x31), 63 BROWSER_TERMINATION_CAUSE(0x34), 64 TEXT_ATTRIBUTE(0x50); 65 66 private int mValue; 67 68 ComprehensionTlvTag(int value) { 69 mValue = value; 70 } 71 72 /** 73 * Returns the actual value of this COMPREHENSION-TLV object. 74 * 75 * @return Actual tag value of this object 76 */ 77 public int value() { 78 return mValue; 79 } 80 81 public static ComprehensionTlvTag fromInt(int value) { 82 for (ComprehensionTlvTag e : ComprehensionTlvTag.values()) { 83 if (e.mValue == value) { 84 return e; 85 } 86 } 87 return null; 88 } 89} 90 91class RilMessage { 92 int mId; 93 Object mData; 94 ResultCode mResCode; 95 96 RilMessage(int msgId, String rawData) { 97 mId = msgId; 98 mData = rawData; 99 } 100 101 RilMessage(RilMessage other) { 102 this.mId = other.mId; 103 this.mData = other.mData; 104 this.mResCode = other.mResCode; 105 } 106} 107 108/** 109 * Class that implements SIM Toolkit Telephony Service. Interacts with the RIL 110 * and application. 111 * 112 * {@hide} 113 */ 114public class StkService extends Handler implements AppInterface { 115 116 // Class members 117 private static SIMRecords mSimRecords; 118 119 // Service members. 120 private static StkService sInstance; 121 private CommandsInterface mCmdIf; 122 private Context mContext; 123 private StkCmdMessage mCurrntCmd = null; 124 private StkCmdMessage mMenuCmd = null; 125 126 private RilMessageDecoder mMsgDecoder = null; 127 128 // Service constants. 129 static final int MSG_ID_SESSION_END = 1; 130 static final int MSG_ID_PROACTIVE_COMMAND = 2; 131 static final int MSG_ID_EVENT_NOTIFY = 3; 132 static final int MSG_ID_CALL_SETUP = 4; 133 static final int MSG_ID_REFRESH = 5; 134 static final int MSG_ID_RESPONSE = 6; 135 136 static final int MSG_ID_RIL_MSG_DECODED = 10; 137 138 // Events to signal SIM presence or absent in the device. 139 private static final int MSG_ID_SIM_LOADED = 20; 140 141 private static final int DEV_ID_KEYPAD = 0x01; 142 private static final int DEV_ID_DISPLAY = 0x02; 143 private static final int DEV_ID_EARPIECE = 0x03; 144 private static final int DEV_ID_UICC = 0x81; 145 private static final int DEV_ID_TERMINAL = 0x82; 146 private static final int DEV_ID_NETWORK = 0x83; 147 148 /* Intentionally private for singleton */ 149 private StkService(CommandsInterface ci, SIMRecords sr, Context context, 150 SIMFileHandler fh, SimCard sc) { 151 if (ci == null || sr == null || context == null || fh == null 152 || sc == null) { 153 throw new NullPointerException( 154 "Service: Input parameters must not be null"); 155 } 156 mCmdIf = ci; 157 mContext = context; 158 159 // Get the RilMessagesDecoder for decoding the messages. 160 mMsgDecoder = RilMessageDecoder.getInstance(this, fh); 161 162 // Register ril events handling. 163 mCmdIf.setOnStkSessionEnd(this, MSG_ID_SESSION_END, null); 164 mCmdIf.setOnStkProactiveCmd(this, MSG_ID_PROACTIVE_COMMAND, null); 165 mCmdIf.setOnStkEvent(this, MSG_ID_EVENT_NOTIFY, null); 166 mCmdIf.setOnStkCallSetUp(this, MSG_ID_CALL_SETUP, null); 167 //mCmdIf.setOnSimRefresh(this, MSG_ID_REFRESH, null); 168 169 mSimRecords = sr; 170 171 // Register for SIM ready event. 172 mSimRecords.registerForRecordsLoaded(this, MSG_ID_SIM_LOADED, null); 173 174 mCmdIf.reportStkServiceIsRunning(null); 175 StkLog.d(this, "StkService: is running"); 176 } 177 178 public void dispose() { 179 mSimRecords.unregisterForRecordsLoaded(this); 180 mCmdIf.unSetOnStkSessionEnd(this); 181 mCmdIf.unSetOnStkProactiveCmd(this); 182 mCmdIf.unSetOnStkEvent(this); 183 mCmdIf.unSetOnStkCallSetUp(this); 184 185 this.removeCallbacksAndMessages(null); 186 } 187 188 protected void finalize() { 189 StkLog.d(this, "Service finalized"); 190 } 191 192 private void handleRilMsg(RilMessage rilMsg) { 193 if (rilMsg == null) { 194 return; 195 } 196 197 // dispatch messages 198 CommandParams cmdParams = null; 199 switch (rilMsg.mId) { 200 case MSG_ID_EVENT_NOTIFY: 201 if (rilMsg.mResCode == ResultCode.OK) { 202 cmdParams = (CommandParams) rilMsg.mData; 203 if (cmdParams != null) { 204 handleProactiveCommand(cmdParams); 205 } 206 } 207 break; 208 case MSG_ID_PROACTIVE_COMMAND: 209 cmdParams = (CommandParams) rilMsg.mData; 210 if (cmdParams != null) { 211 if (rilMsg.mResCode == ResultCode.OK) { 212 handleProactiveCommand(cmdParams); 213 } else { 214 // for proactive commands that couldn't be decoded 215 // successfully respond with the code generated by the 216 // message decoder. 217 sendTerminalResponse(cmdParams.cmdDet, rilMsg.mResCode, 218 false, 0, null); 219 } 220 } 221 break; 222 case MSG_ID_REFRESH: 223 cmdParams = (CommandParams) rilMsg.mData; 224 if (cmdParams != null) { 225 handleProactiveCommand(cmdParams); 226 } 227 break; 228 case MSG_ID_SESSION_END: 229 handleSessionEnd(); 230 break; 231 case MSG_ID_CALL_SETUP: 232 // prior event notify command supplied all the information 233 // needed for set up call processing. 234 break; 235 } 236 } 237 238 /** 239 * Handles RIL_UNSOL_STK_PROACTIVE_COMMAND unsolicited command from RIL. 240 * Sends valid proactive command data to the application using intents. 241 * 242 */ 243 private void handleProactiveCommand(CommandParams cmdParams) { 244 StkLog.d(this, cmdParams.getCommandType().name()); 245 246 StkCmdMessage cmdMsg = new StkCmdMessage(cmdParams); 247 switch (cmdParams.getCommandType()) { 248 case SET_UP_MENU: 249 if (removeMenu(cmdMsg.getMenu())) { 250 mMenuCmd = null; 251 } else { 252 mMenuCmd = cmdMsg; 253 } 254 sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false, 0, 255 null); 256 break; 257 case DISPLAY_TEXT: 258 // when application is not required to respond, send an immediate 259 // response. 260 if (!cmdMsg.geTextMessage().responseNeeded) { 261 sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false, 262 0, null); 263 } 264 break; 265 case REFRESH: 266 // ME side only handles refresh commands which meant to remove IDLE 267 // MODE TEXT. 268 cmdParams.cmdDet.typeOfCommand = CommandType.SET_UP_IDLE_MODE_TEXT 269 .value(); 270 break; 271 case SET_UP_IDLE_MODE_TEXT: 272 sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false, 273 0, null); 274 break; 275 case LAUNCH_BROWSER: 276 case SELECT_ITEM: 277 case GET_INPUT: 278 case GET_INKEY: 279 case SEND_DTMF: 280 case SEND_SMS: 281 case SEND_SS: 282 case SEND_USSD: 283 case PLAY_TONE: 284 case SET_UP_CALL: 285 // nothing to do on telephony! 286 break; 287 default: 288 StkLog.d(this, "Unsupported command"); 289 return; 290 } 291 mCurrntCmd = cmdMsg; 292 Intent intent = new Intent(AppInterface.STK_CMD_ACTION); 293 intent.putExtra("STK CMD", cmdMsg); 294 mContext.sendBroadcast(intent); 295 } 296 297 /** 298 * Handles RIL_UNSOL_STK_SESSION_END unsolicited command from RIL. 299 * 300 */ 301 private void handleSessionEnd() { 302 StkLog.d(this, "SESSION END"); 303 304 mCurrntCmd = mMenuCmd; 305 Intent intent = new Intent(AppInterface.STK_SESSION_END_ACTION); 306 mContext.sendBroadcast(intent); 307 } 308 309 private void sendTerminalResponse(CommandDetails cmdDet, 310 ResultCode resultCode, boolean includeAdditionalInfo, 311 int additionalInfo, ResponseData resp) { 312 313 if (cmdDet == null) { 314 return; 315 } 316 ByteArrayOutputStream buf = new ByteArrayOutputStream(); 317 318 // command details 319 int tag = ComprehensionTlvTag.COMMAND_DETAILS.value(); 320 if (cmdDet.compRequired) { 321 tag |= 0x80; 322 } 323 buf.write(tag); 324 buf.write(0x03); // length 325 buf.write(cmdDet.commandNumber); 326 buf.write(cmdDet.typeOfCommand); 327 buf.write(cmdDet.commandQualifier); 328 329 // device identities 330 tag = 0x80 | ComprehensionTlvTag.DEVICE_IDENTITIES.value(); 331 buf.write(tag); 332 buf.write(0x02); // length 333 buf.write(DEV_ID_TERMINAL); // source device id 334 buf.write(DEV_ID_UICC); // destination device id 335 336 // result 337 tag = 0x80 | ComprehensionTlvTag.RESULT.value(); 338 buf.write(tag); 339 int length = includeAdditionalInfo ? 2 : 1; 340 buf.write(length); 341 buf.write(resultCode.value()); 342 343 // additional info 344 if (includeAdditionalInfo) { 345 buf.write(additionalInfo); 346 } 347 348 // Fill optional data for each corresponding command 349 if (resp != null) { 350 resp.format(buf); 351 } 352 353 byte[] rawData = buf.toByteArray(); 354 String hexString = IccUtils.bytesToHexString(rawData); 355 if (Config.LOGD) { 356 StkLog.d(this, "TERMINAL RESPONSE: " + hexString); 357 } 358 359 mCmdIf.sendTerminalResponse(hexString, null); 360 } 361 362 363 private void sendMenuSelection(int menuId, boolean helpRequired) { 364 365 ByteArrayOutputStream buf = new ByteArrayOutputStream(); 366 367 // tag 368 int tag = BerTlv.BER_MENU_SELECTION_TAG; 369 buf.write(tag); 370 371 // length 372 buf.write(0x00); // place holder 373 374 // device identities 375 tag = 0x80 | ComprehensionTlvTag.DEVICE_IDENTITIES.value(); 376 buf.write(tag); 377 buf.write(0x02); // length 378 buf.write(DEV_ID_KEYPAD); // source device id 379 buf.write(DEV_ID_UICC); // destination device id 380 381 // item identifier 382 tag = 0x80 | ComprehensionTlvTag.ITEM_ID.value(); 383 buf.write(tag); 384 buf.write(0x01); // length 385 buf.write(menuId); // menu identifier chosen 386 387 // help request 388 if (helpRequired) { 389 tag = ComprehensionTlvTag.HELP_REQUEST.value(); 390 buf.write(tag); 391 buf.write(0x00); // length 392 } 393 394 byte[] rawData = buf.toByteArray(); 395 396 // write real length 397 int len = rawData.length - 2; // minus (tag + length) 398 rawData[1] = (byte) len; 399 400 String hexString = IccUtils.bytesToHexString(rawData); 401 402 mCmdIf.sendEnvelope(hexString, null); 403 } 404 405 private void eventDownload(int event, int sourceId, int destinationId, 406 byte[] additionalInfo, boolean oneShot) { 407 408 ByteArrayOutputStream buf = new ByteArrayOutputStream(); 409 410 // tag 411 int tag = BerTlv.BER_EVENT_DOWNLOAD_TAG; 412 buf.write(tag); 413 414 // length 415 buf.write(0x00); // place holder, assume length < 128. 416 417 // event list 418 tag = 0x80 | ComprehensionTlvTag.EVENT_LIST.value(); 419 buf.write(tag); 420 buf.write(0x01); // length 421 buf.write(event); // event value 422 423 // device identities 424 tag = 0x80 | ComprehensionTlvTag.DEVICE_IDENTITIES.value(); 425 buf.write(tag); 426 buf.write(0x02); // length 427 buf.write(sourceId); // source device id 428 buf.write(destinationId); // destination device id 429 430 // additional information 431 if (additionalInfo != null) { 432 for (byte b : additionalInfo) { 433 buf.write(b); 434 } 435 } 436 437 byte[] rawData = buf.toByteArray(); 438 439 // write real length 440 int len = rawData.length - 2; // minus (tag + length) 441 rawData[1] = (byte) len; 442 443 String hexString = IccUtils.bytesToHexString(rawData); 444 445 mCmdIf.sendEnvelope(hexString, null); 446 } 447 448 /** 449 * Used for instantiating/updating the Service from the GsmPhone constructor. 450 * 451 * @param ci CommandsInterface object 452 * @param sr SIMRecords object 453 * @param context phone app context 454 * @param fh SIM file handler 455 * @param sc GSM SIM card 456 * @return The only Service object in the system 457 */ 458 public static StkService getInstance(CommandsInterface ci, SIMRecords sr, 459 Context context, SIMFileHandler fh, SimCard sc) { 460 if (sInstance == null) { 461 if (ci == null || sr == null || context == null || fh == null 462 || sc == null) { 463 return null; 464 } 465 HandlerThread thread = new HandlerThread("Stk Telephony service"); 466 thread.start(); 467 sInstance = new StkService(ci, sr, context, fh, sc); 468 StkLog.d(sInstance, "NEW sInstance"); 469 } else if ((sr != null) && (mSimRecords != sr)) { 470 StkLog.d(sInstance, String.format( 471 "Reinitialize the Service with SIMRecords sr=0x%x.", sr)); 472 mSimRecords = sr; 473 474 // re-Register for SIM ready event. 475 mSimRecords.registerForRecordsLoaded(sInstance, MSG_ID_SIM_LOADED, null); 476 StkLog.d(sInstance, "sr changed reinitialize and return current sInstance"); 477 } else { 478 StkLog.d(sInstance, "Return current sInstance"); 479 } 480 return sInstance; 481 } 482 483 /** 484 * Used by application to get an AppInterface object. 485 * 486 * @return The only Service object in the system 487 */ 488 public static AppInterface getInstance() { 489 return getInstance(null, null, null, null, null); 490 } 491 492 @Override 493 public void handleMessage(Message msg) { 494 495 switch (msg.what) { 496 case MSG_ID_SESSION_END: 497 case MSG_ID_PROACTIVE_COMMAND: 498 case MSG_ID_EVENT_NOTIFY: 499 case MSG_ID_REFRESH: 500 StkLog.d(this, "ril message arrived"); 501 String data = null; 502 if (msg.obj != null) { 503 AsyncResult ar = (AsyncResult) msg.obj; 504 if (ar != null && ar.result != null) { 505 try { 506 data = (String) ar.result; 507 } catch (ClassCastException e) { 508 break; 509 } 510 } 511 } 512 mMsgDecoder.sendStartDecodingMessageParams(new RilMessage(msg.what, data)); 513 break; 514 case MSG_ID_CALL_SETUP: 515 mMsgDecoder.sendStartDecodingMessageParams(new RilMessage(msg.what, null)); 516 break; 517 case MSG_ID_SIM_LOADED: 518 break; 519 case MSG_ID_RIL_MSG_DECODED: 520 handleRilMsg((RilMessage) msg.obj); 521 break; 522 case MSG_ID_RESPONSE: 523 handleCmdResponse((StkResponseMessage) msg.obj); 524 break; 525 default: 526 throw new AssertionError("Unrecognized STK command: " + msg.what); 527 } 528 } 529 530 public synchronized void onCmdResponse(StkResponseMessage resMsg) { 531 if (resMsg == null) { 532 return; 533 } 534 // queue a response message. 535 Message msg = this.obtainMessage(MSG_ID_RESPONSE, resMsg); 536 msg.sendToTarget(); 537 } 538 539 private boolean validateResponse(StkResponseMessage resMsg) { 540 if (mCurrntCmd != null) { 541 return (resMsg.cmdDet.compareTo(mCurrntCmd.mCmdDet)); 542 } 543 return false; 544 } 545 546 private boolean removeMenu(Menu menu) { 547 try { 548 if (menu.items.size() == 1 && menu.items.get(0) == null) { 549 return true; 550 } 551 } catch (NullPointerException e) { 552 StkLog.d(this, "Unable to get Menu's items size"); 553 return true; 554 } 555 return false; 556 } 557 558 private void handleCmdResponse(StkResponseMessage resMsg) { 559 // Make sure the response details match the last valid command. An invalid 560 // response is a one that doesn't have a corresponding proactive command 561 // and sending it can "confuse" the baseband/ril. 562 // One reason for out of order responses can be UI glitches. For example, 563 // if the application launch an activity, and that activity is stored 564 // by the framework inside the history stack. That activity will be 565 // available for relaunch using the latest application dialog 566 // (long press on the home button). Relaunching that activity can send 567 // the same command's result again to the StkService and can cause it to 568 // get out of sync with the SIM. 569 if (!validateResponse(resMsg)) { 570 return; 571 } 572 ResponseData resp = null; 573 boolean helpRequired = false; 574 CommandDetails cmdDet = resMsg.getCmdDetails(); 575 576 switch (resMsg.resCode) { 577 case HELP_INFO_REQUIRED: 578 helpRequired = true; 579 // fall through 580 case OK: 581 case PRFRMD_WITH_PARTIAL_COMPREHENSION: 582 case PRFRMD_WITH_MISSING_INFO: 583 case PRFRMD_WITH_ADDITIONAL_EFS_READ: 584 case PRFRMD_ICON_NOT_DISPLAYED: 585 case PRFRMD_MODIFIED_BY_NAA: 586 case PRFRMD_LIMITED_SERVICE: 587 case PRFRMD_WITH_MODIFICATION: 588 case PRFRMD_NAA_NOT_ACTIVE: 589 case PRFRMD_TONE_NOT_PLAYED: 590 switch (AppInterface.CommandType.fromInt(cmdDet.typeOfCommand)) { 591 case SET_UP_MENU: 592 helpRequired = resMsg.resCode == ResultCode.HELP_INFO_REQUIRED; 593 sendMenuSelection(resMsg.usersMenuSelection, helpRequired); 594 return; 595 case SELECT_ITEM: 596 resp = new SelectItemResponseData(resMsg.usersMenuSelection); 597 break; 598 case GET_INPUT: 599 case GET_INKEY: 600 Input input = mCurrntCmd.geInput(); 601 if (!input.yesNo) { 602 // when help is requested there is no need to send the text 603 // string object. 604 if (!helpRequired) { 605 resp = new GetInkeyInputResponseData(resMsg.usersInput, 606 input.ucs2, input.packed); 607 } 608 } else { 609 resp = new GetInkeyInputResponseData( 610 resMsg.usersYesNoSelection); 611 } 612 break; 613 case DISPLAY_TEXT: 614 case LAUNCH_BROWSER: 615 break; 616 case SET_UP_CALL: 617 mCmdIf.handleCallSetupRequestFromSim(resMsg.usersConfirm, null); 618 // No need to send terminal response for SET UP CALL. The user's 619 // confirmation result is send back using a dedicated ril message 620 // invoked by the CommandInterface call above. 621 mCurrntCmd = null; 622 return; 623 } 624 break; 625 case NO_RESPONSE_FROM_USER: 626 case UICC_SESSION_TERM_BY_USER: 627 case BACKWARD_MOVE_BY_USER: 628 resp = null; 629 break; 630 default: 631 return; 632 } 633 sendTerminalResponse(cmdDet, resMsg.resCode, false, 0, resp); 634 mCurrntCmd = null; 635 } 636} 637