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.gsm.stk; 18 19 import android.content.Context; 20 import android.content.Intent; 21 import android.os.AsyncResult; 22 import android.os.Handler; 23 import android.os.HandlerThread; 24 import android.os.Message; 25 26 import com.android.internal.telephony.IccUtils; 27 import com.android.internal.telephony.CommandsInterface; 28 import com.android.internal.telephony.gsm.SimCard; 29 import com.android.internal.telephony.gsm.SIMFileHandler; 30 import com.android.internal.telephony.gsm.SIMRecords; 31 32 import android.util.Config; 33 34 import 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 */ 42 enum 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 91 class 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 */ 114 public 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, "Reinitialize the Service with SIMRecords"); 471 mSimRecords = sr; 472 473 // re-Register for SIM ready event. 474 mSimRecords.registerForRecordsLoaded(sInstance, MSG_ID_SIM_LOADED, null); 475 StkLog.d(sInstance, "sr changed reinitialize and return current sInstance"); 476 } else { 477 StkLog.d(sInstance, "Return current sInstance"); 478 } 479 return sInstance; 480 } 481 482 /** 483 * Used by application to get an AppInterface object. 484 * 485 * @return The only Service object in the system 486 */ 487 public static AppInterface getInstance() { 488 return getInstance(null, null, null, null, null); 489 } 490 491 @Override 492 public void handleMessage(Message msg) { 493 494 switch (msg.what) { 495 case MSG_ID_SESSION_END: 496 case MSG_ID_PROACTIVE_COMMAND: 497 case MSG_ID_EVENT_NOTIFY: 498 case MSG_ID_REFRESH: 499 StkLog.d(this, "ril message arrived"); 500 String data = null; 501 if (msg.obj != null) { 502 AsyncResult ar = (AsyncResult) msg.obj; 503 if (ar != null && ar.result != null) { 504 try { 505 data = (String) ar.result; 506 } catch (ClassCastException e) { 507 break; 508 } 509 } 510 } 511 mMsgDecoder.sendStartDecodingMessageParams(new RilMessage(msg.what, data)); 512 break; 513 case MSG_ID_CALL_SETUP: 514 mMsgDecoder.sendStartDecodingMessageParams(new RilMessage(msg.what, null)); 515 break; 516 case MSG_ID_SIM_LOADED: 517 break; 518 case MSG_ID_RIL_MSG_DECODED: 519 handleRilMsg((RilMessage) msg.obj); 520 break; 521 case MSG_ID_RESPONSE: 522 handleCmdResponse((StkResponseMessage) msg.obj); 523 break; 524 default: 525 throw new AssertionError("Unrecognized STK command: " + msg.what); 526 } 527 } 528 529 public synchronized void onCmdResponse(StkResponseMessage resMsg) { 530 if (resMsg == null) { 531 return; 532 } 533 // queue a response message. 534 Message msg = this.obtainMessage(MSG_ID_RESPONSE, resMsg); 535 msg.sendToTarget(); 536 } 537 538 private boolean validateResponse(StkResponseMessage resMsg) { 539 if (mCurrntCmd != null) { 540 return (resMsg.cmdDet.compareTo(mCurrntCmd.mCmdDet)); 541 } 542 return false; 543 } 544 545 private boolean removeMenu(Menu menu) { 546 try { 547 if (menu.items.size() == 1 && menu.items.get(0) == null) { 548 return true; 549 } 550 } catch (NullPointerException e) { 551 StkLog.d(this, "Unable to get Menu's items size"); 552 return true; 553 } 554 return false; 555 } 556 557 private void handleCmdResponse(StkResponseMessage resMsg) { 558 // Make sure the response details match the last valid command. An invalid 559 // response is a one that doesn't have a corresponding proactive command 560 // and sending it can "confuse" the baseband/ril. 561 // One reason for out of order responses can be UI glitches. For example, 562 // if the application launch an activity, and that activity is stored 563 // by the framework inside the history stack. That activity will be 564 // available for relaunch using the latest application dialog 565 // (long press on the home button). Relaunching that activity can send 566 // the same command's result again to the StkService and can cause it to 567 // get out of sync with the SIM. 568 if (!validateResponse(resMsg)) { 569 return; 570 } 571 ResponseData resp = null; 572 boolean helpRequired = false; 573 CommandDetails cmdDet = resMsg.getCmdDetails(); 574 575 switch (resMsg.resCode) { 576 case HELP_INFO_REQUIRED: 577 helpRequired = true; 578 // fall through 579 case OK: 580 case PRFRMD_WITH_PARTIAL_COMPREHENSION: 581 case PRFRMD_WITH_MISSING_INFO: 582 case PRFRMD_WITH_ADDITIONAL_EFS_READ: 583 case PRFRMD_ICON_NOT_DISPLAYED: 584 case PRFRMD_MODIFIED_BY_NAA: 585 case PRFRMD_LIMITED_SERVICE: 586 case PRFRMD_WITH_MODIFICATION: 587 case PRFRMD_NAA_NOT_ACTIVE: 588 case PRFRMD_TONE_NOT_PLAYED: 589 switch (AppInterface.CommandType.fromInt(cmdDet.typeOfCommand)) { 590 case SET_UP_MENU: 591 helpRequired = resMsg.resCode == ResultCode.HELP_INFO_REQUIRED; 592 sendMenuSelection(resMsg.usersMenuSelection, helpRequired); 593 return; 594 case SELECT_ITEM: 595 resp = new SelectItemResponseData(resMsg.usersMenuSelection); 596 break; 597 case GET_INPUT: 598 case GET_INKEY: 599 Input input = mCurrntCmd.geInput(); 600 if (!input.yesNo) { 601 // when help is requested there is no need to send the text 602 // string object. 603 if (!helpRequired) { 604 resp = new GetInkeyInputResponseData(resMsg.usersInput, 605 input.ucs2, input.packed); 606 } 607 } else { 608 resp = new GetInkeyInputResponseData( 609 resMsg.usersYesNoSelection); 610 } 611 break; 612 case DISPLAY_TEXT: 613 case LAUNCH_BROWSER: 614 break; 615 case SET_UP_CALL: 616 mCmdIf.handleCallSetupRequestFromSim(resMsg.usersConfirm, null); 617 // No need to send terminal response for SET UP CALL. The user's 618 // confirmation result is send back using a dedicated ril message 619 // invoked by the CommandInterface call above. 620 mCurrntCmd = null; 621 return; 622 } 623 break; 624 case NO_RESPONSE_FROM_USER: 625 case UICC_SESSION_TERM_BY_USER: 626 case BACKWARD_MOVE_BY_USER: 627 resp = null; 628 break; 629 default: 630 return; 631 } 632 sendTerminalResponse(cmdDet, resMsg.resCode, false, 0, resp); 633 mCurrntCmd = null; 634 } 635 } 636