Bug 744714 - Part 3: RIL implementation. r=philikon
authorYoshi Huang <yhuang@mozilla.com>
Tue, 10 Apr 2012 20:04:27 +0800
changeset 105807 a5611e4202ef91bc2b9df184f6df430e6f2db352
parent 105806 86069e9122f9ef3db3af2cf7b695bc24326e9ca8
child 105808 1cf18897cdf418ccc8159e2de34302dc4ae92fff
push id55
push usershu@rfrn.org
push dateThu, 30 Aug 2012 01:33:09 +0000
reviewersphilikon
bugs744714
milestone18.0a1
Bug 744714 - Part 3: RIL implementation. r=philikon
dom/icc/src/IccManager.cpp
dom/icc/src/IccManager.h
dom/network/interfaces/nsIMobileConnectionProvider.idl
dom/system/gonk/RILContentHelper.js
dom/system/gonk/RadioInterfaceLayer.js
dom/system/gonk/ril_consts.js
dom/system/gonk/ril_worker.js
--- a/dom/icc/src/IccManager.cpp
+++ b/dom/icc/src/IccManager.cpp
@@ -1,16 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsIDOMClassInfo.h"
 #include "IccManager.h"
 #include "SimToolKit.h"
 
+#define NS_RILCONTENTHELPER_CONTRACTID "@mozilla.org/ril/content-helper;1"
+
 DOMCI_DATA(MozIccManager, mozilla::dom::icc::IccManager)
 
 namespace mozilla {
 namespace dom {
 namespace icc {
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(IccManager)
 
@@ -19,29 +21,37 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
   NS_CYCLE_COLLECTION_TRAVERSE_EVENT_HANDLER(stkcommand)
   NS_CYCLE_COLLECTION_TRAVERSE_EVENT_HANDLER(stksessionend)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(IccManager,
                                                 nsDOMEventTargetHelper)
   NS_CYCLE_COLLECTION_UNLINK_EVENT_HANDLER(stkcommand)
   NS_CYCLE_COLLECTION_UNLINK_EVENT_HANDLER(stksessionend)
+  tmp->mProvider = nullptr;
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(IccManager)
   NS_INTERFACE_MAP_ENTRY(nsIDOMMozIccManager)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMMozIccManager)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(MozIccManager)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper)
 
 NS_IMPL_ADDREF_INHERITED(IccManager, nsDOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(IccManager, nsDOMEventTargetHelper)
 
 IccManager::IccManager()
 {
+  mProvider = do_GetService(NS_RILCONTENTHELPER_CONTRACTID);
+
+  // Not being able to acquire the provider isn't fatal since we check
+  // for it explicitly below.
+  if (!mProvider) {
+    NS_WARNING("Could not acquire nsIMobileConnectionProvider!");
+  }
 }
 
 void
 IccManager::Init(nsPIDOMWindow* aWindow)
 {
   BindToOwner(aWindow);
 }
 
@@ -60,23 +70,33 @@ IccManager::Observe(nsISupports* aSubjec
   return NS_OK;
 }
 
 // nsIDOMMozIccManager
 
 NS_IMETHODIMP
 IccManager::SendStkResponse(const JS::Value& aResponse)
 {
-  return NS_ERROR_NOT_IMPLEMENTED;
+  if (!mProvider) {
+    return NS_ERROR_FAILURE;
+  }
+
+  mProvider->SendStkResponse(GetOwner(), aResponse);
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 IccManager::SendStkMenuSelection(PRUint16 aItemIdentifier, bool aHelpRequested)
 {
-  return NS_ERROR_NOT_IMPLEMENTED;
+  if (!mProvider) {
+    return NS_ERROR_FAILURE;
+  }
+
+  mProvider->SendStkMenuSelection(GetOwner(), aItemIdentifier, aHelpRequested);
+  return NS_OK;
 }
 
 NS_IMPL_EVENT_HANDLER(IccManager, stkcommand)
 NS_IMPL_EVENT_HANDLER(IccManager, stksessionend)
 
 } // namespace icc
 } // namespace dom
 } // namespace mozilla
--- a/dom/icc/src/IccManager.h
+++ b/dom/icc/src/IccManager.h
@@ -3,16 +3,17 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_icc_IccManager_h
 #define mozilla_dom_icc_IccManager_h
 
 #include "nsCycleCollectionParticipant.h"
 #include "nsDOMEventTargetHelper.h"
 #include "nsIDOMIccManager.h"
+#include "nsIMobileConnectionProvider.h"
 #include "nsIObserver.h"
 
 namespace mozilla {
 namespace dom {
 namespace icc {
 
 class IccManager : public nsDOMEventTargetHelper
                  , public nsIDOMMozIccManager
@@ -29,16 +30,18 @@ public:
 
   void Init(nsPIDOMWindow *aWindow);
   void Shutdown();
 
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(IccManager,
                                            nsDOMEventTargetHelper)
 
 private:
+  nsCOMPtr<nsIMobileConnectionProvider> mProvider;
+
   NS_DECL_EVENT_HANDLER(stkcommand)
   NS_DECL_EVENT_HANDLER(stksessionend)
 };
 
 } // namespace icc
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/network/interfaces/nsIMobileConnectionProvider.idl
+++ b/dom/network/interfaces/nsIMobileConnectionProvider.idl
@@ -23,11 +23,18 @@ interface nsIMobileConnectionProvider : 
 
   nsIDOMDOMRequest getNetworks(in nsIDOMWindow window);
   nsIDOMDOMRequest selectNetwork(in nsIDOMWindow window, in nsIDOMMozMobileNetworkInfo network);
   nsIDOMDOMRequest selectNetworkAutomatically(in nsIDOMWindow window);
 
   nsIDOMDOMRequest getCardLock(in nsIDOMWindow window, in DOMString lockType);
   nsIDOMDOMRequest unlockCardLock(in nsIDOMWindow window, in jsval info);
   nsIDOMDOMRequest setCardLock(in nsIDOMWindow window, in jsval info);
+
   nsIDOMDOMRequest sendUSSD(in nsIDOMWindow window, in DOMString ussd);
   nsIDOMDOMRequest cancelUSSD(in nsIDOMWindow window);
+
+  void sendStkResponse(in nsIDOMWindow window,
+                       in jsval        response);
+  void sendStkMenuSelection(in nsIDOMWindow   window,
+                            in unsigned short itemIdentifier,
+                            in boolean        helpRequested);
 };
--- a/dom/system/gonk/RILContentHelper.js
+++ b/dom/system/gonk/RILContentHelper.js
@@ -51,23 +51,27 @@ const RIL_IPC_MSG_NAMES = [
   "RIL:VoicemailNotification",
   "RIL:VoicemailNumberChanged",
   "RIL:CallError",
   "RIL:CardLockResult",
   "RIL:UssdReceived",
   "RIL:SendUssd:Return:OK",
   "RIL:SendUssd:Return:KO",
   "RIL:CancelUssd:Return:OK",
-  "RIL:CancelUssd:Return:KO"
+  "RIL:CancelUssd:Return:KO",
+  "RIL:StkCommand",
+  "RIL:StkSessionEnd"
 ];
 
 const kVoiceChangedTopic     = "mobile-connection-voice-changed";
 const kDataChangedTopic      = "mobile-connection-data-changed";
 const kCardStateChangedTopic = "mobile-connection-cardstate-changed";
 const kUssdReceivedTopic     = "mobile-connection-ussd-received";
+const kStkCommandTopic       = "icc-manager-stk-command";
+const kStkSessionEndTopic    = "icc-manager-stk-session-end";
 
 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
                                    "@mozilla.org/childprocessmessagemanager;1",
                                    "nsISyncMessageSender");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator",
                                    "@mozilla.org/uuid-generator;1",
                                    "nsIUUIDGenerator");
@@ -372,16 +376,35 @@ RILContentHelper.prototype = {
                                  Cr.NS_ERROR_UNEXPECTED);
     }
     let request = Services.DOMRequest.createRequest(window);
     let requestId = this.getRequestId(request);
     cpmm.sendAsyncMessage("RIL:CancelUSSD", {requestId: requestId});
     return request;
   },
 
+  sendStkResponse: function sendStkResponse(window, response) {
+    if (window == null) {
+      throw Components.Exception("Can't get window object",
+                                  Cr.NS_ERROR_UNEXPECTED);
+    }
+    cpmm.sendAsyncMessage("RIL:SendStkResponse", response);
+  },
+
+  sendStkMenuSelection: function sendStkMenuSelection(window,
+                                                      itemIdentifier,
+                                                      helpRequested) {
+    if (window == null) {
+      throw Components.Exception("Can't get window object",
+                                  Cr.NS_ERROR_UNEXPECTED);
+    }
+    cpmm.sendAsyncMessage("RIL:SendStkMenuSelection", {itemIdentifier: itemIdentifier,
+                                                       helpRequested: helpRequested});
+  },
+
   _telephonyCallbacks: null,
   _voicemailCallbacks: null,
   _enumerateTelephonyCallbacks: null,
 
   voicemailStatus: null,
   voicemailNumber: null,
   voicemailDisplayName: null,
 
@@ -624,16 +647,23 @@ RILContentHelper.prototype = {
         break;
       case "RIL:SendUssd:Return:KO":
       case "RIL:CancelUssd:Return:KO":
         request = this.takeRequest(msg.json.requestId);
         if (request) {
           Services.DOMRequest.fireError(request, msg.json.errorMsg);
         }
         break;
+      case "RIL:StkCommand":
+        let jsonString = JSON.stringify(msg.json);
+        Services.obs.notifyObservers(null, kStkCommandTopic, jsonString);
+        break;
+      case "RIL:StkSessionEnd":
+        Services.obs.notifyObservers(null, kStkSessionEndTopic, null);
+      break;
     }
   },
 
   handleEnumerateCalls: function handleEnumerateCalls(calls) {
     debug("handleEnumerateCalls: " + JSON.stringify(calls));
     let callback = this._enumerationTelephonyCallbacks.shift();
     for (let i in calls) {
       let call = calls[i];
--- a/dom/system/gonk/RadioInterfaceLayer.js
+++ b/dom/system/gonk/RadioInterfaceLayer.js
@@ -57,17 +57,19 @@ const RIL_IPC_MSG_NAMES = [
   "RIL:ResumeCall",
   "RIL:GetAvailableNetworks",
   "RIL:SelectNetwork",
   "RIL:SelectNetworkAuto",
   "RIL:GetCardLock",
   "RIL:UnlockCardLock",
   "RIL:SetCardLock",
   "RIL:SendUSSD",
-  "RIL:CancelUSSD"
+  "RIL:CancelUSSD",
+  "RIL:SendStkResponse",
+  "RIL:SendStkMenuSelection"
 ];
 
 XPCOMUtils.defineLazyServiceGetter(this, "gSmsService",
                                    "@mozilla.org/sms/smsservice;1",
                                    "nsISmsService");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gSmsRequestManager",
                                    "@mozilla.org/sms/smsrequestmanager;1",
@@ -298,16 +300,22 @@ RadioInterfaceLayer.prototype = {
       case "RIL:SendUSSD":
         this.saveRequestTarget(msg);
         this.sendUSSD(msg.json);
         break;
       case "RIL:CancelUSSD":
         this.saveRequestTarget(msg);
         this.cancelUSSD(msg.json);
         break;
+      case "RIL:SendStkResponse":
+        this.sendStkResponse(msg.json);
+        break;
+      case "RIL:SendStkMenuSelection":
+        this.sendStkMenuSelection(msg.json);
+        break;
     }
   },
 
   onerror: function onerror(event) {
     debug("Got an error: " + event.filename + ":" +
           event.lineno + ": " + event.message + "\n");
     event.preventDefault();
   },
@@ -435,16 +443,22 @@ RadioInterfaceLayer.prototype = {
         this.handleUSSDReceived(message);
         break;
       case "sendussd":
         this.handleSendUSSD(message);
         break;
       case "cancelussd":
         this.handleCancelUSSD(message);
         break;
+      case "stkcommand":
+        this.handleStkProactiveCommand(message);
+        break;
+      case "stksessionend":
+        ppmm.broadcastAsyncMessage("RIL:StkSessionEnd", null);
+        break;
       default:
         throw new Error("Don't know about this message type: " +
                         message.rilMessageType);
     }
   },
 
   _messageManagerByRequest: null,
   saveRequestTarget: function saveRequestTarget(msg) {
@@ -1007,16 +1021,21 @@ RadioInterfaceLayer.prototype = {
 
   handleCancelUSSD: function handleCancelUSSD(message) {
     debug("handleCancelUSSD " + JSON.stringify(message));
     let messageType = message.success ? "RIL:CancelUssd:Return:OK" :
                                         "RIL:CancelUssd:Return:KO";
     this._sendRequestResults(messageType, message);
   },
 
+  handleStkProactiveCommand: function handleStkProactiveCommand(message) {
+    debug("handleStkProactiveCommand " + JSON.stringify(message));
+    ppmm.broadcastAsyncMessage("RIL:StkCommand", message);
+  },
+
   // nsIObserver
 
   observe: function observe(subject, topic, data) {
     switch (topic) {
       case kMozSettingsChangedObserverTopic:
         let setting = JSON.parse(data);
         this.handle(setting.key, setting.value);
         break;
@@ -1177,16 +1196,25 @@ RadioInterfaceLayer.prototype = {
                              requestId: requestId});
   },
 
   selectNetwork: function selectNetwork(message) {
     message.rilMessageType = "selectNetwork";
     this.worker.postMessage(message);
   },
 
+  sendStkResponse: function sendStkResponse(message) {
+    message.rilMessageType = "sendStkTerminalResponse";
+    this.worker.postMessage(message);
+  },
+
+  sendStkMenuSelection: function sendStkMenuSelection(message) {
+    message.rilMessageType = "sendStkMenuSelection";
+    this.worker.postMessage(message);
+  },
 
   get microphoneMuted() {
     return gAudioManager.microphoneMuted;
   },
   set microphoneMuted(value) {
     if (value == this.microphoneMuted) {
       return;
     }
--- a/dom/system/gonk/ril_consts.js
+++ b/dom/system/gonk/ril_consts.js
@@ -506,16 +506,204 @@ const ICC_USIM_EFPBC_TAG   = 0xc5;
 const ICC_USIM_EFGRP_TAG   = 0xc6;
 const ICC_USIM_EFAAS_TAG   = 0xc7;
 const ICC_USIM_EFGSD_TAG   = 0xc8;
 const ICC_USIM_EFUID_TAG   = 0xc9;
 const ICC_USIM_EFEMAIL_TAG = 0xca;
 const ICC_USIM_EFCCP1_TAG  = 0xcb;
 
 /**
+ * STK constants.
+ */
+//  Tags for Ber Tlv.
+const BER_UNKNOWN_TAG = 0x00;
+const BER_PROACTIVE_COMMAND_TAG = 0xd0;
+const BER_MENU_SELECTION_TAG = 0xd3;
+const BER_EVENT_DOWNLOAD_TAG = 0xd6;
+
+// Flags in Comprehension TLV.
+const COMPREHENSIONTLV_FLAG_CR = 0x80;  // Comprehension required.
+
+// Tags for Comprehension TLV.
+const COMPREHENSIONTLV_TAG_COMMAND_DETAILS = 0x01;
+const COMPREHENSIONTLV_TAG_DEVICE_ID = 0x02;
+const COMPREHENSIONTLV_TAG_RESULT = 0x03;
+const COMPREHENSIONTLV_TAG_DURATION = 0x04;
+const COMPREHENSIONTLV_TAG_ALPHA_ID = 0x05;
+const COMPREHENSIONTLV_TAG_ADDRESS = 0x06;
+const COMPREHENSIONTLV_TAG_TEXT_STRING = 0x0d;
+const COMPREHENSIONTLV_TAG_ITEM = 0x0f;
+const COMPREHENSIONTLV_TAG_ITEM_ID = 0x10;
+const COMPREHENSIONTLV_TAG_RESPONSE_LENGTH = 0x11;
+const COMPREHENSIONTLV_TAG_HELP_REQUEST = 0x15;
+const COMPREHENSIONTLV_TAG_DEFAULT_TEXT = 0x17;
+const COMPREHENSIONTLV_TAG_ICON_ID = 0x1e;
+const COMPREHENSIONTLV_TAG_ICON_ID_LIST = 0x1f;
+const COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE = 0x2b;
+const COMPREHENSIONTLV_TAG_URL = 0x31;
+
+// Device identifiers, see TS 11.14, clause 12.7
+const STK_DEVICE_ID_KEYPAD = 0x01;
+const STK_DEVICE_ID_DISPLAY = 0x02;
+const STK_DEVICE_ID_EARPIECE = 0x03;
+const STK_DEVICE_ID_SIM = 0x81;
+const STK_DEVICE_ID_ME = 0x82;
+const STK_DEVICE_ID_NETWORK = 0x83;
+
+// STK Proactive commands.
+const STK_CMD_REFRESH = 0x01;
+const STK_CMD_SET_UP_CALL = 0x10;
+const STK_CMD_SEND_SS = 0x11;
+const STK_CMD_SEND_USSD = 0x12;
+const STK_CMD_SEND_SMS = 0x13;
+const STK_CMD_SEND_DTMF = 0x14;
+const STK_CMD_LAUNCH_BROWSER = 0x15;
+const STK_CMD_DISPLAY_TEXT = 0x21;
+const STK_CMD_GET_INKEY = 0x22;
+const STK_CMD_GET_INPUT = 0x23;
+const STK_CMD_SELECT_ITEM = 0x24;
+const STK_CMD_SET_UP_MENU = 0x25;
+const STK_CMD_SET_UP_IDLE_MODE_TEXT = 0x28;
+
+// STK Result code.
+// TS 11.14, clause 12.12
+
+// Results '0X' and '1X' indicate that the command has been performed.
+
+// Command performed successfully.
+const STK_RESULT_OK = 0x00;
+
+// Command performed with partial comprehension.
+const STK_RESULT_PRFRMD_WITH_PARTIAL_COMPREHENSION = 0x01;
+
+// Command performed, with missing information.
+const STK_RESULT_PRFRMD_WITH_MISSING_INFO = 0x02;
+
+// REFRESH performed with additional EFs read.
+const STK_RESULT_PRFRMD_WITH_ADDITIONAL_EFS_READ = 0x03;
+
+// Command performed successfully, but requested icon could not be
+// displayed.
+const STK_RESULT_PRFRMD_ICON_NOT_DISPLAYED = 0x04;
+
+// Command performed, but modified by call control by NAA.
+const STK_RESULT_PRFRMD_MODIFIED_BY_NAA = 0x05;
+
+// Command performed successfully, limited service.
+const STK_RESULT_PRFRMD_LIMITED_SERVICE = 0x06;
+
+// Command performed with modification.
+const STK_RESULT_PRFRMD_WITH_MODIFICATION = 0x07;
+
+// REFRESH performed but indicated NAA was not active.
+const STK_RESULT_PRFRMD_NAA_NOT_ACTIVE = 0x08;
+
+// Command performed successfully; tone not played.
+const STK_RESULT_PRFRMD_TONE_NOT_PLAYED = 0x09;
+
+// Proactive UICC session terminated by the user.
+const STK_RESULT_UICC_SESSION_TERM_BY_USER = 0x10;
+
+// Backward move in the proactive UICC session requested by the user.
+const STK_RESULT_BACKWARD_MOVE_BY_USER = 0x11;
+
+// No response from user.
+const STK_RESULT_NO_RESPONSE_FROM_USER = 0x12;
+
+// Help information required by the user.
+const STK_RESULT_HELP_INFO_REQUIRED = 0x13;
+
+// USSD or SS transaction terminated by the user.
+const STK_RESULT_USSD_SS_SESSION_TERM_BY_USER = 0x14;
+
+// Results '2X' indicate to the UICC that it may be worth re-trying the
+// command at a later opportunity.
+
+// Terminal currently unable to process command.
+const STK_RESULT_TERMINAL_CRNTLY_UNABLE_TO_PROCESS = 0x20;
+
+// Network currently unable to process command.
+const STK_RESULT_NETWORK_CRNTLY_UNABLE_TO_PROCESS = 0x21;
+
+// User did not accept the proactive command.
+const STK_RESULT_USER_NOT_ACCEPT = 0x22;
+
+// User cleared down call before connection or network release.
+const STK_RESULT_USER_CLEAR_DOWN_CALL = 0x23;
+
+// Action in contradiction with the current timer state.
+const STK_RESULT_CONTRADICTION_WITH_TIMER = 0x24;
+
+// Interaction with call control by NAA; temporary problem.
+const STK_RESULT_NAA_CALL_CONTROL_TEMPORARY = 0x25;
+
+// Launch browser generic error code.
+const STK_RESULT_LAUNCH_BROWSER_ERROR = 0x26;
+
+// MMS temporary problem.
+const STK_RESULT_MMS_TEMPORARY = 0x27;
+
+// Results '3X' indicate that it is not worth the UICC re-trying with an
+// identical command; as it will only get the same response. However, the
+// decision to retry lies with the application.
+
+// Command beyond terminal's capabilities.
+const STK_RESULT_BEYOND_TERMINAL_CAPABILITY = 0x30;
+
+// Command type not understood by terminal.
+const STK_RESULT_CMD_TYPE_NOT_UNDERSTOOD = 0x31;
+
+// Command data not understood by terminal.
+const STK_RESULT_CMD_DATA_NOT_UNDERSTOOD = 0x32;
+
+// Command number not known by terminal.
+const STK_RESULT_CMD_NUM_NOT_KNOWN = 0x33;
+
+// SS Return Error.
+const STK_RESULT_SS_RETURN_ERROR = 0x34;
+
+// SMS RP-ERROR.
+const STK_RESULT_SMS_RP_ERROR = 0x35;
+
+// Error, required values are missing.
+const STK_RESULT_REQUIRED_VALUES_MISSING = 0x36;
+
+// USSD Return Error.
+const STK_RESULT_USSD_RETURN_ERROR = 0x37;
+
+// MultipleCard commands error.
+const STK_RESULT_MULTI_CARDS_CMD_ERROR = 0x38;
+
+// Interaction with call control by USIM or MO short message control by
+// USIM; permanent problem.
+const STK_RESULT_USIM_CALL_CONTROL_PERMANENT = 0x39;
+
+// Bearer Independent Protocol error.
+const STK_RESULT_BIP_ERROR = 0x3a;
+
+// Access Technology unable to process command.
+const STK_RESULT_ACCESS_TECH_UNABLE_TO_PROCESS = 0x3b;
+
+// Frames error.
+const STK_RESULT_FRAMES_ERROR = 0x3c;
+
+// MMS Error.
+const STK_RESULT_MMS_ERROR = 0x3d;
+
+// STK presentation types, TS 11.14, clause 12.6, Command Qualifier: Select Item
+const STK_PRESENTATION_TYPE_NOT_SPECIFIED = 0x00; // Bit 1 is 0.
+const STK_PRESENTATION_TYPE_DATA_VALUES = 0x01; // Bit 1 is 1, bit 2 is 0.
+const STK_PRESENTATION_TYPE_NAVIGATION_OPTIONS = 0x03; // Bit 1 is 1, bit 2 is 1.
+
+// STK Coding Scheme.
+const STK_TEXT_CODING_GSM_7BIT_PACKED = 0x00;
+const STK_TEXT_CODING_GSM_8BIT = 0x04;
+const STK_TEXT_CODING_UCS2 = 0x08;
+
+/**
  * GSM PDU constants
  */
 
 // PDU TYPE-OF-ADDRESS
 const PDU_TOA_UNKNOWN       = 0x80; // Unknown. This is used when the user or
                                     // network has no a priori information
                                     // about the numbering plan.
 const PDU_TOA_ISDN          = 0x81; // ISDN/Telephone numbering
--- a/dom/system/gonk/ril_worker.js
+++ b/dom/system/gonk/ril_worker.js
@@ -1929,16 +1929,208 @@ let RIL = {
   /**
    * Cancel pending USSD.
    */
    cancelUSSD: function cancelUSSD(options) {
      Buf.simpleRequest(REQUEST_CANCEL_USSD, options);
    },
 
   /**
+   * Handle STK CALL_SET_UP request.
+   *
+   * @param hasConfirmed
+   *        Does use have confirmed the call requested from ICC?
+   */
+  stkHandleCallSetup: function stkHandleCallSetup(options) {
+     Buf.newParcel(REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM, options);
+     Buf.writeUint32(1);
+     Buf.writeUint32(options.hasConfirmed ? 1 : 0);
+     Buf.sendParcel();
+  },
+
+  /**
+   * Send STK terminal response.
+   *
+   * @param commandNumber
+   * @param typeOfCommand
+   * @param commandQualifier
+   * @param resultCode
+   * @param [optional] itemIdentifier
+   * @param [optional] input
+   * @param [optional] isYesNo
+   * @param [optional] isUCS2
+   * @param [optional] isPacked
+   * @param [optional] hasConfirmed
+   */
+  sendStkTerminalResponse: function sendStkTerminalResponse(response) {
+    if (response.hasConfirmed !== undefined) {
+      this.stkHandleCallSetup(response);
+      return;
+    }
+
+    let token = Buf.newParcel(REQUEST_STK_SEND_TERMINAL_RESPONSE);
+    let textLen = 0;
+    if (response.resultCode != STK_RESULT_HELP_INFO_REQUIRED) {
+      if (response.isYesNo) {
+        textLen = 1;
+      } else if (response.input) {
+        if (response.isUCS2) {
+          textLen = response.input.length * 2;
+        } else if (response.isPacked) {
+          let bits = response.input.length * 7;
+          textLen = bits * 7 / 8 + (bits % 8 ? 1 : 0);
+        } else {
+          textLen = response.input.length;
+        }
+      }
+    }
+
+    // 1 octets = 2 chars.
+    let size = (5 + /* Size of Command Details TLV */
+                4 + /* Size of Device Identifier TLV */
+                3 + /* Size of Result */
+                (response.itemIdentifier ? 3 : 0) +
+                (textLen ? textLen + 3 : 0)) * 2;
+    Buf.writeUint32(size);
+
+    // Command Details
+    GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_COMMAND_DETAILS |
+                               COMPREHENSIONTLV_FLAG_CR);
+    GsmPDUHelper.writeHexOctet(3);
+    GsmPDUHelper.writeHexOctet(response.commandNumber);
+    GsmPDUHelper.writeHexOctet(response.typeOfCommand);
+    GsmPDUHelper.writeHexOctet(response.commandQualifier);
+
+    // Device Identifier
+    // According to TS102.223/TS31.111 section 6.8 Structure of
+    // TERMINAL RESPONSE, "For all SIMPLE-TLV objects with Min=N,
+    // the ME should set the CR(comprehension required) flag to
+    // comprehension not required.(CR=0)"
+    // Since DEVICE_IDENTITIES and DURATION TLVs have Min=N,
+    // the CR flag is not set.
+    GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DEVICE_ID);
+    GsmPDUHelper.writeHexOctet(2);
+    GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_ME);
+    GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_SIM);
+
+    // Result
+    GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_RESULT |
+                               COMPREHENSIONTLV_FLAG_CR);
+    GsmPDUHelper.writeHexOctet(1);
+    GsmPDUHelper.writeHexOctet(response.resultCode);
+
+    // Item Identifier
+    if (response.itemIdentifier) {
+      GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ITEM_ID |
+                                 COMPREHENSIONTLV_FLAG_CR);
+      GsmPDUHelper.writeHexOctet(1);
+      GsmPDUHelper.writeHexOctet(response.itemIdentifier);
+    }
+
+    // No need to process Text data if user requests help information.
+    if (response.resultCode != STK_RESULT_HELP_INFO_REQUIRED) {
+      let text;
+      if (response.isYesNo !== undefined) {
+        // GET_INKEY
+        // When the ME issues a successful TERMINAL RESPONSE for a GET INKEY
+        // ("Yes/No") command with command qualifier set to "Yes/No", it shall
+        // supply the value '01' when the answer is "positive" and the value 
+        // '00' when the answer is "negative" in the Text string data object.
+        text = response.isYesNo ? 0x01 : 0x00;
+      } else {
+        text = response.input;
+      }
+
+      if (text) {
+        GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TEXT_STRING |
+                                   COMPREHENSIONTLV_FLAG_CR);
+        GsmPDUHelper.writeHexOctet(textLen + 1); // +1 for coding
+        let coding = response.isUCS2 ? STK_TEXT_CODING_UCS2 :
+                       (response.isPacked ? STK_TEXT_CODING_GSM_7BIT_PACKED :
+                          STK_TEXT_CODING_GSM_8BIT);
+        GsmPDUHelper.writeHexOctet(coding);
+
+        // Write Text String.
+        switch (coding) {
+          case STK_TEXT_CODING_UCS2:
+            GsmPDUHelper.writeUCS2String(text);
+            break;
+          case STK_TEXT_CODING_GSM_7BIT_PACKED:
+            GsmPDUHelper.writeStringAsSeptets(text, 0, 0, 0);
+            break;
+          case STK_TEXT_CODING_GSM_8BIT:
+            for (let i = 0; i < textLen; i++) {
+              GsmPDUHelper.writeHexOctet(text.charCodeAt(i));
+            }
+            break;
+        }
+      }
+    }
+
+    Buf.writeUint32(0);
+    Buf.sendParcel();
+  },
+
+  /**
+   * Send STK Envelope(Menu Selection) command.
+   *
+   * @param itemIdentifier
+   * @param helpRequested
+   */
+  sendStkMenuSelection: function sendStkMenuSelection(command) {
+    this.sendICCEnvelopeCommand(command);
+  },
+
+  /**
+   * Send REQUEST_STK_SEND_ENVELOPE_COMMAND to ICC.
+   *
+   * @param [optioanl] itemIdentifier
+   * @param [optional] helpRequested
+   */
+  sendICCEnvelopeCommand: function sendICCEnvelopeCommand(options) {
+    let token = Buf.newParcel(REQUEST_STK_SEND_ENVELOPE_COMMAND);
+    let berLen = 4 + /* Size of Device Identifier TLV */
+                 (options.itemIdentifier ? 3 : 0) +
+                 (options.helpRequested ? 2 : 0);
+    let size = (2 + berLen) * 2;
+
+    Buf.writeUint32(size);
+
+    // Write a BER-TLV
+    GsmPDUHelper.writeHexOctet(BER_MENU_SELECTION_TAG);
+    GsmPDUHelper.writeHexOctet(berLen);
+
+    // Device Identifies
+    GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DEVICE_ID |
+                               COMPREHENSIONTLV_FLAG_CR);
+    GsmPDUHelper.writeHexOctet(2);
+    GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_KEYPAD);
+    GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_SIM);
+
+    // Item Identifier
+    if (options.itemIdentifier) {
+      GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ITEM_ID |
+                                 COMPREHENSIONTLV_FLAG_CR);
+      GsmPDUHelper.writeHexOctet(1);
+      GsmPDUHelper.writeHexOctet(options.itemIdentifier);
+    }
+
+    // Help Request
+    if (options.helpRequested) {
+      GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_HELP_REQUEST |
+                                 COMPREHENSIONTLV_FLAG_CR);
+      GsmPDUHelper.writeHexOctet(0);
+      // Help Request doesn't have value
+    }
+
+    Buf.writeUint32(0);
+    Buf.sendParcel();
+  },
+
+  /**
    * Check a given number against the list of emergency numbers provided by the RIL.
    *
    * @param number
    *        The number to look up.
    */
    _isEmergencyNumber: function _isEmergencyNumber(number) {
      // Check read-write ecclist property first.
      let numbers = libcutils.property_get("ril.ecclist");
@@ -2872,16 +3064,73 @@ let RIL = {
     for each (let datacall in this.currentDataCalls) {
       datacall_list.push(datacall);
     }
     this.sendDOMMessage({rilMessageType: "datacalllist",
                          datacalls: datacall_list});
   },
 
   /**
+   * Process STK Proactive Command.
+   */
+  processStkProactiveCommand: function processStkProactiveCommand() {
+    let length = Buf.readUint32();
+    let berTlv = BerTlvHelper.decode(length / 2);
+    Buf.readStringDelimiter(length);
+
+    let ctlvs = berTlv.value;
+    let ctlv = StkProactiveCmdHelper.searchForTag(
+        COMPREHENSIONTLV_TAG_COMMAND_DETAILS, ctlvs);
+    if (!ctlv) {
+      RIL.sendStkTerminalResponse({
+        commandNumber: 0,
+        typeOfCommand: 0,
+        commandQualifier: 0,
+        resultCode: STK_RESULT_CMD_DATA_NOT_UNDERSTOOD});
+      throw new Error("Can't find COMMAND_DETAILS ComprehensionTlv");
+    }
+
+    let cmdDetails = ctlv.value;
+    if (DEBUG) {
+      debug("commandNumber = " + cmdDetails.commandNumber +
+           " typeOfCommand = " + cmdDetails.typeOfCommand.toString(16) +
+           " commandQualifier = " + cmdDetails.commandQualifier);
+    }
+
+    let param = StkCommandParamsFactory.createParam(cmdDetails, ctlvs);
+    if (param) {
+      switch (cmdDetails.typeOfCommand) {
+        case STK_CMD_SET_UP_MENU:
+        case STK_CMD_SET_UP_IDLE_MODE_TEXT:
+          this.sendStkTerminalResponse({commandNumber: cmdDetails.commandNumber,
+                                        typeOfCommand: cmdDetails.typeOfCommand,
+                                        commandQualifier: cmdDetails.commandQualifier,
+                                        resultCode: STK_RESULT_OK});
+          break;
+        case STK_CMD_DISPLAY_TEXT:
+          if (!param.responseNeeded) {
+            this.sendStkTerminalResponse({commandNumber: cmdDetails.commandNumber,
+                                          typeOfCommand: cmdDetails.typeOfCommand,
+                                          commandQualifier: cmdDetails.commandQualifier,
+                                          resultCode: STK_RESULT_OK});
+          }
+          break;
+        default:
+          break;
+      }
+
+      RIL.sendDOMMessage({rilMessageType: "stkcommand",
+                          commandNumber: cmdDetails.commandNumber,
+                          typeOfCommand: cmdDetails.typeOfCommand,
+                          commandQualifier: cmdDetails.commandQualifier,
+                          options: param});
+    }
+  },
+
+  /**
    * Send messages to the main thread.
    */
   sendDOMMessage: function sendDOMMessage(message) {
     postMessage(message, "*");
   },
 
   /**
    * Handle incoming requests from the RIL. We find the method that
@@ -3716,18 +3965,23 @@ RIL[UNSOLICITED_SIGNAL_STRENGTH] = funct
 RIL[UNSOLICITED_DATA_CALL_LIST_CHANGED] = function UNSOLICITED_DATA_CALL_LIST_CHANGED(length) {
   if (RILQUIRKS_V5_LEGACY) {
     this.getDataCallList();
     return;
   }
   this[REQUEST_DATA_CALL_LIST](length, {rilRequestError: ERROR_SUCCESS});
 };
 RIL[UNSOLICITED_SUPP_SVC_NOTIFICATION] = null;
-RIL[UNSOLICITED_STK_SESSION_END] = null;
-RIL[UNSOLICITED_STK_PROACTIVE_COMMAND] = null;
+
+RIL[UNSOLICITED_STK_SESSION_END] = function UNSOLICITED_STK_SESSION_END() {
+  this.sendDOMMessage({rilMessageType: "stksessionend"});
+};
+RIL[UNSOLICITED_STK_PROACTIVE_COMMAND] = function UNSOLICITED_STK_PROACTIVE_COMMAND() {
+  this.processStkProactiveCommand();
+};
 RIL[UNSOLICITED_STK_EVENT_NOTIFY] = null;
 RIL[UNSOLICITED_STK_CALL_SETUP] = null;
 RIL[UNSOLICITED_SIM_SMS_STORAGE_FULL] = null;
 RIL[UNSOLICITED_SIM_REFRESH] = null;
 RIL[UNSOLICITED_CALL_RING] = function UNSOLICITED_CALL_RING() {
   let info;
   let isCDMA = false; //XXX TODO hard-code this for now
   if (isCDMA) {
@@ -5128,16 +5382,746 @@ let GsmPDUHelper = {
         this.writeUCS2String(body);
         break;
     }
 
     // End of the string. The string length is always even by definition, so
     // we write two \0 delimiters.
     Buf.writeUint16(0);
     Buf.writeUint16(0);
+  },
+};
+
+let StkCommandParamsFactory = {
+  createParam: function createParam(cmdDetails, ctlvs) {
+    let param;
+    switch (cmdDetails.typeOfCommand) {
+      case STK_CMD_SET_UP_MENU:
+      case STK_CMD_SELECT_ITEM:
+        param = this.processSelectItem(cmdDetails, ctlvs);
+        break;
+      case STK_CMD_DISPLAY_TEXT:
+        param = this.processDisplayText(cmdDetails, ctlvs);
+        break;
+      case STK_CMD_SET_UP_IDLE_MODE_TEXT:
+        param = this.processSetUpIdleModeText(cmdDetails, ctlvs);
+        break;
+      case STK_CMD_GET_INKEY:
+        param = this.processGetInkey(cmdDetails, ctlvs);
+        break;
+      case STK_CMD_GET_INPUT:
+        param = this.processGetInput(cmdDetails, ctlvs);
+        break;
+      case STK_CMD_SEND_SS:
+      case STK_CMD_SEND_USSD:
+      case STK_CMD_SEND_SMS:
+      case STK_CMD_SEND_DTMF:
+        param = this.processEventNotify(cmdDetails, ctlvs);
+        break;
+      case STK_CMD_SET_UP_CALL:
+        param = this.processSetupCall(cmdDetails, ctlvs);
+        break;
+      case STK_LAUNCH_BROWSER:
+        param = this.processLaunchBrowser(cmdDetails, ctlvs);
+        break;
+      default:
+        debug("unknown proactive command");
+        break;
+    }
+    return param;
+  },
+
+  /**
+   * Construct a param for Select Item.
+   *
+   * @param cmdDetails
+   *        The value object of CommandDetails TLV.
+   * @param ctlvs
+   *        The all TLVs in this proactive command.
+   */
+  processSelectItem: function processSelectItem(cmdDetails, ctlvs) {
+    let menu = {};
+
+    let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs);
+    if (ctlv) {
+      menu.title = ctlv.value.identifier;
+    }
+
+    menu.items = [];
+    for (let i = 0; i < ctlvs.length; i++) {
+      let ctlv = ctlvs[i];
+      if (ctlv.tag == COMPREHENSIONTLV_TAG_ITEM) {
+        menu.items.push(ctlv.value);
+      }
+    }
+
+    if (menu.items.length == 0) {
+      RIL.sendStkTerminalResponse({
+          commandNumber: cmdDetails.commandNumber,
+          typeOfCommand: cmdDetails.typeOfCommand,
+          commandQualifier: cmdDetails.commandQualifier,
+          resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
+      throw new Error("Stk Menu: Required value missing : items");
+    }
+
+    ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_ITEM_ID, ctlvs);
+    if (ctlv) {
+      menu.defaultItem = ctlv.value.identifier - 1;
+    }
+
+    // The 1st bit and 2nd bit determines the presentation type.
+    menu.presentationType = cmdDetails.commandQualifier & 0x03;
+
+    // Help information available.
+    if (cmdDetails.commandQualifier & 0x80) {
+      menu.isHelpAvailable = true;
+    }
+
+    return menu;
+  },
+
+  processDisplayText: function processDisplayText(cmdDetails, ctlvs) {
+    let textMsg = {};
+
+    let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_TEXT_STRING, ctlvs);
+    if (!ctlv) {
+      RIL.sendStkTerminalResponse({
+        commandNumber: cmdDetails.commandNumber,
+        typeOfCommand: cmdDetails.typeOfCommand,
+        commandQualifier: cmdDetails.commandQualifier,
+        resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
+      throw new Error("Stk Display Text: Required value missing : Text String");
+    }
+    textMsg.text = ctlv.value.text;
+
+    ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE, ctlvs);
+    if (ctlv) {
+      textMsg.responseNeeded = true;
+    }
+
+    // High priority.
+    if (cmdDetails.commandQualifier & 0x01) {
+      textMsg.isHighPriority = true;
+    }
+
+    // User clear.
+    if (cmdDetails.commandQualifier & 0x80) {
+      textMsg.userClear = true;
+    }
+
+    return textMsg;
+  },
+
+  processSetUpIdleModeText: function processSetUpIdleModeText(cmdDetails, ctlvs) {
+    let textMsg = {};
+
+    let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_TEXT_STRING, ctlvs);
+    if (!ctlv) {
+      RIL.sendStkTerminalResponse({
+        commandNumber: cmdDetails.commandNumber,
+        typeOfCommand: cmdDetails.typeOfCommand,
+        commandQualifier: cmdDetails.commandQualifier,
+        resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
+      throw new Error("Stk Set Up Idle Text: Required value missing : Text String");
+    }
+    textMsg.text = ctlv.value.text;
+
+    return textMsg;
+  },
+
+  processGetInkey: function processGetInkey(cmdDetails, ctlvs) {
+    let input = {};
+
+    let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_TEXT_STRING, ctlvs);
+    if (!ctlv) {
+      RIL.sendStkTerminalResponse({
+        commandNumber: cmdDetails.commandNumber,
+        typeOfCommand: cmdDetails.typeOfCommand,
+        commandQualifier: cmdDetails.commandQualifier,
+        resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
+      throw new Error("Stk Get InKey: Required value missing : Text String");
+    }
+    input.text = ctlv.value.text;
+
+    input.minLength = 1;
+    input.maxLength = 1;
+
+    // isAlphabet
+    if (cmdDetails.commandQualifier & 0x01) {
+      input.isAlphabet = true;
+    }
+
+    // UCS2
+    if (cmdDetails.commandQualifier & 0x02) {
+      input.isUCS2 = true;
+    }
+
+    // Character sets defined in bit 1 and bit 2 are disable and
+    // the YES/NO reponse is required.
+    if (cmdDetails.commandQualifier & 0x04) {
+      input.isYesNoRequested = true;
+    }
+
+    // Help information available.
+    if (cmdDetails.commandQualifier & 0x80) {
+      input.isHelpAvailable = true;
+    }
+
+    return input;
+  },
+
+  processGetInput: function processGetInput(cmdDetails, ctlvs) {
+    let input = {};
+
+    let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_TEXT_STRING, ctlvs);
+    if (!ctlv) {
+      RIL.sendStkTerminalResponse({
+        commandNumber: cmdDetails.commandNumber,
+        typeOfCommand: cmdDetails.typeOfCommand,
+        commandQualifier: cmdDetails.commandQualifier,
+        resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
+      throw new Error("Stk Get Input: Required value missing : Text String");
+    }
+    input.text = ctlv.value.text;
+
+    ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_RESPONSE_LENGTH, ctlvs);
+    if (ctlv) {
+      input.minLength = ctlv.value.minLength;
+      input.maxLength = ctlv.value.maxLength;
+    }
+
+    ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_DEFAULT_TEXT, ctlvs);
+    if (ctlv) {
+      input.defaultText = ctlv.value.text;
+    }
+
+    // Alphabet only
+    if (cmdDetails.commandQualifier & 0x01) {
+      input.isAlphabet = true;
+    }
+
+    // UCS2
+    if (cmdDetails.commandQualifier & 0x02) {
+      input.isUCS2 = true;
+    }
+
+    // User input shall not be revealed
+    if (cmdDetails.commandQualifier & 0x04) {
+      input.hideInput = true;
+    }
+
+    // User input in SMS packed format
+    if (cmdDetails.commandQualifier & 0x08) {
+      input.isPacked = true;
+    }
+
+    // Help information available.
+    if (cmdDetails.commandQualifier & 0x80) {
+      input.isHelpAvailable = true;
+    }
+
+    return input;
+  },
+
+  processEventNotify: function processEventNotify(cmdDetails, ctlvs) {
+    let textMsg = {};
+
+    let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs);
+    if (!ctlv) {
+      RIL.sendStkTerminalResponse({
+        commandNumber: cmdDetails.commandNumber,
+        typeOfCommand: cmdDetails.typeOfCommand,
+        commandQualifier: cmdDetails.commandQualifier,
+        resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
+      throw new Error("Stk Event Notfiy: Required value missing : Alpha ID");
+    }
+    textMsg.text = ctlv.value.identifier;
+
+    return textMsg;
+  },
+
+  processSetupCall: function processSetupCall(cmdDetails, ctlvs) {
+    let call = {};
+
+    for (let i = 0; i < ctlvs.length; i++) {
+      let ctlv = ctlvs[i];
+      if (ctlv.tag == COMPREHENSIONTLV_TAG_ALPHA_ID) {
+        if (!call.confirmMessage) {
+          call.confirmMessage = ctlv.value.identifier;
+        } else {
+          call.callMessge = ctlv.value.identifier;
+          break;
+        }
+      }
+    }
+
+    let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_ADDRESS, ctlvs);
+    if (!ctlv) {
+      RIL.sendStkTerminalResponse({
+        commandNumber: cmdDetails.commandNumber,
+        typeOfCommand: cmdDetails.typeOfCommand,
+        commandQualifier: cmdDetails.commandQualifier,
+        resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
+      throw new Error("Stk Set Up Call: Required value missing : Adress");
+    }
+    call.address = ctlv.value.number;
+
+    return call;
+  },
+
+  processLaunchBrowser: function processLaunchBrowser(cmdDetails, ctlvs) {
+    let browser = {};
+
+    let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_URL, ctlvs);
+    if (!ctlv) {
+      RIL.sendStkTerminalResponse({
+        commandNumber: cmdDetails.commandNumber,
+        typeOfCommand: cmdDetails.typeOfCommand,
+        commandQualifier: cmdDetails.commandQualifier,
+        resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
+      throw new Error("Stk Launch Browser: Required value missing : URL");
+    }
+    browser.url = ctlv.value.url;
+
+    ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs)
+    if (ctlv) {
+      browser.confirmMessage = ctlv.value.identifier;
+    }
+
+    browser.mode = cmdDetails.commandQualifier & 0x03;
+
+    return browser;
+  }
+};
+
+let StkProactiveCmdHelper = {
+  retrieve: function retrieve(tag, length) {
+    switch (tag) {
+      case COMPREHENSIONTLV_TAG_COMMAND_DETAILS:
+        return this.retrieveCommandDetails(length);
+      case COMPREHENSIONTLV_TAG_DEVICE_ID:
+        return this.retrieveDeviceId(length);
+      case COMPREHENSIONTLV_TAG_ALPHA_ID:
+        return this.retrieveAlphaId(length);
+      case COMPREHENSIONTLV_TAG_ADDRESS:
+        return this.retrieveAddress(length);
+      case COMPREHENSIONTLV_TAG_TEXT_STRING:
+        return this.retrieveTextString(length);
+      case COMPREHENSIONTLV_TAG_ITEM:
+        return this.retrieveItem(length);
+      case COMPREHENSIONTLV_TAG_ITEM_ID:
+        return this.retrieveItemId(length);
+      case COMPREHENSIONTLV_TAG_RESPONSE_LENGTH:
+        return this.retrieveResponseLength(length);
+      case COMPREHENSIONTLV_TAG_DEFAULT_TEXT:
+        return this.retrieveDefaultText(length);
+      case COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE:
+        return this.retrieveImmediaResponse(length);
+      case COMPREHENSIONTLV_TAG_URL:
+        return this.retrieveUrl(length);
+      default:
+        debug("StkProactiveCmdHelper: unknown tag " + tag.toString(16));
+        Buf.seekIncoming(length * PDU_HEX_OCTET_SIZE);
+        return null;
+    }
+  },
+
+  /**
+   * Command Details.
+   *
+   * | Byte | Description         | Length |
+   * |  1   | Command details Tag |   1    |
+   * |  2   | Length = 03         |   1    |
+   * |  3   | Command number      |   1    |
+   * |  4   | Type of Command     |   1    |
+   * |  5   | Command Qualifier   |   1    |
+   */
+  retrieveCommandDetails: function retrieveCommandDetails(length) {
+    let cmdDetails = {
+      commandNumber: GsmPDUHelper.readHexOctet(),
+      typeOfCommand: GsmPDUHelper.readHexOctet(),
+      commandQualifier: GsmPDUHelper.readHexOctet()
+    };
+    return cmdDetails;
+  },
+
+  /**
+   * Device Identities.
+   *
+   * | Byte | Description            | Length |
+   * |  1   | Device Identity Tag    |   1    |
+   * |  2   | Length = 02            |   1    |
+   * |  3   | Source device Identity |   1    |
+   * |  4   | Destination device Id  |   1    |
+   */
+  retrieveDeviceId: function retrieveDeviceId(length) {
+    let deviceId = {
+      sourceId: GsmPDUHelper.readHexOctet(),
+      destinationId: GsmPDUHelper.readHexOctet()
+    };
+    return deviceId;
+  },
+
+  /**
+   * Alpha Identifier.
+   *
+   * | Byte         | Description            | Length |
+   * |  1           | Alpha Identifier Tag   |   1    |
+   * | 2 ~ (Y-1)+2  | Length (X)             |   Y    |
+   * | (Y-1)+3 ~    | Alpha identfier        |   X    |
+   * | (Y-1)+X+2    |                        |        |
+   */
+  retrieveAlphaId: function retrieveAlphaId(length) {
+    let alphaId = {
+      identifier: GsmPDUHelper.readAlphaIdentifier(length)
+    };
+    return alphaId;
+  },
+
+  /**
+   * Address.
+   *
+   * | Byte         | Description            | Length |
+   * |  1           | Alpha Identifier Tag   |   1    |
+   * | 2 ~ (Y-1)+2  | Length (X)             |   Y    |
+   * | (Y-1)+3      | TON and NPI            |   1    |
+   * | (Y-1)+4 ~    | Dialling number        |   X    |
+   * | (Y-1)+X+2    |                        |        |
+   */
+  retrieveAddress: function retrieveAddress(length) {
+    let address = {
+      number : GsmPDUHelper.readDiallingNumber(length)
+    };
+    return address;
+  },
+
+  /**
+   * Text String.
+   *
+   * | Byte         | Description        | Length |
+   * |  1           | Text String Tag    |   1    |
+   * | 2 ~ (Y-1)+2  | Length (X)         |   Y    |
+   * | (Y-1)+3      | Data coding scheme |   1    |
+   * | (Y-1)+4~     | Text String        |   X    |
+   * | (Y-1)+X+2    |                    |        |
+   */
+  retrieveTextString: function retrieveTextString(length) {
+    if (!length) {
+      // null string.
+      return null;
+    }
+
+    let text = {
+      codingScheme: GsmPDUHelper.readHexOctet()
+    };
+
+    length--; // -1 for the codingScheme.
+    switch (text.codingScheme & 0x0f) {
+      case STK_TEXT_CODING_GSM_7BIT_PACKED:
+        text.textString = GsmPDUHelper.readSeptetsToString(length / 7, 0, 0, 0);
+        break;
+      case STK_TEXT_CODING_GSM_8BIT:
+        text.textString = GsmPDUHelper.read8BitUnpackedToString(length);
+        break;
+      case STK_TEXT_CODING_UCS2:
+        text.textString = GsmPDUHelper.readUCS2String(length);
+        break;
+    }
+    return text;
+  },
+
+  /**
+   * Item.
+   *
+   * | Byte         | Description            | Length |
+   * |  1           | Item Tag               |   1    |
+   * | 2 ~ (Y-1)+2  | Length (X)             |   Y    |
+   * | (Y-1)+3      | Identifier of item     |   1    |
+   * | (Y-1)+4 ~    | Text string of item    |   X    |
+   * | (Y-1)+X+2    |                        |        |
+   */
+  retrieveItem: function retrieveItem(length) {
+    let item = {
+      identifier: GsmPDUHelper.readHexOctet(),
+      text: GsmPDUHelper.readAlphaIdentifier(length - 1)
+    };
+    return item;
+  },
+
+  /**
+   * Item Identifier.
+   *
+   * | Byte | Description               | Length |
+   * |  1   | Item Identifier Tag       |   1    |
+   * |  2   | Lenth = 01                |   1    |
+   * |  3   | Identifier of Item chosen |   1    |
+   */
+  retrieveItemId: function retrieveItemId(length) {
+    let itemId = {
+      identifier: GsmPDUHelper.readHexOctet()
+    };
+    return itemId;
+  },
+
+  /**
+   * Response Length.
+   *
+   * | Byte | Description                | Length |
+   * |  1   | Response Length Tag        |   1    |
+   * |  2   | Lenth = 02                 |   1    |
+   * |  3   | Minimum length of response |   1    |
+   * |  4   | Maximum length of response |   1    |
+   */
+  retrieveResponseLength: function retrieveResponseLength(length) {
+    let rspLength = {
+      minLength : GsmPDUHelper.readHexOctet(),
+      maxLength : GsmPDUHelper.readHexOctet()
+    };
+    return rspLength;
+  },
+
+  /**
+   * Default Text.
+   *
+   * Same as Text String.
+   */
+  retrieveDefaultText: function retrieveDefaultText(length) {
+    return retrieveTextString(length);
+  },
+
+  /**
+   * Immediate Response.
+   *
+   * | Byte  | Description            | Length |
+   * |  1    | Immediate Response Tag |   1    |
+   * |  2    | Length = 00            |   1    |
+   */
+  retrieveImmediaResponse: function retrieveImmediaResponse(length) {
+    return {};
+  },
+
+  /**
+   * URL
+   *
+   * | Byte      | Description         | Length |
+   * |  1        | URL Tag             |   1    |
+   * | 2 ~ (Y+1) | Length(X)           |   Y    |
+   * | (Y+2) ~   | URL                 |   X    |
+   * | (Y+1+X)   |                     |        |
+   */
+  retrieveUrl: function retrieveUrl(length) {
+    let s = "";
+    for (let i = 0; i < length; i++) {
+      s += String.fromCharCode(GsmPDUHelper.readHexOctet());
+    }
+    return {url: s};
+  },
+
+  searchForTag: function searchForTag(tag, ctlvs) {
+    for (let i = 0; i < ctlvs.length; i++) {
+      let ctlv = ctlvs[i];
+      if ((ctlv.tag & ~COMPREHENSIONTLV_FLAG_CR) == tag) {
+        return ctlv;
+      }
+    }
+    return null;
+  },
+};
+
+let ComprehensionTlvHelper = {
+  /**
+   * Decode raw data to a Comprehension-TLV.
+   */
+  decode: function decode() {
+    let hlen = 0; // For header(tag field + length field) length.
+    let temp = GsmPDUHelper.readHexOctet();
+    hlen++;
+
+    // TS 101.220, clause 7.1.1
+    let tag, tagValue, cr;
+    switch (temp) {
+      // TS 101.220, clause 7.1.1
+      case 0x0: // Not used.
+      case 0xff: // Not used.
+      case 0x80: // Reserved for future use.
+        RIL.sendStkTerminalResponse({
+          commandNumber: 0,
+          typeOfCommand: 0,
+          commandQualifier: 0,
+          resultCode: STK_RESULT_CMD_DATA_NOT_UNDERSTOOD});
+        throw new Error("Invalid octet when parsing Comprehension TLV :" + temp);
+        break;
+      case 0x7f: // Tag is three byte format.
+        // TS 101.220 clause 7.1.1.2.
+        // | Byte 1 | Byte 2                        | Byte 3 |
+        // |        | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |        |
+        // | 0x7f   |CR | Tag Value                          |
+        tag = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
+        hlen += 2;
+        cr = (tag & 0x8000) != 0;
+        tag &= ~0x8000;
+        break;
+      default: // Tag is single byte format.
+        tag = temp;
+        // TS 101.220 clause 7.1.1.1.
+        // | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |
+        // |CR | Tag Value                 |
+        cr = (tag & 0x80) != 0;
+        tag &= ~0x80;
+    }
+
+    // TS 101.220 clause 7.1.2, Length Encoding.
+    // Length   |  Byte 1  | Byte 2 | Byte 3 | Byte 4 |
+    // 0 - 127  |  00 - 7f | N/A    | N/A    | N/A    |
+    // 128-255  |  81      | 80 - ff| N/A    | N/A    |
+    // 256-65535|  82      | 0100 - ffff     | N/A    |
+    // 65536-   |  83      |     010000 - ffffff      |
+    // 16777215
+    //
+    // Length errors: TS 11.14, clause 6.10.6
+
+    let length; // Data length.
+    temp = GsmPDUHelper.readHexOctet();
+    hlen++;
+    if (temp < 0x80) {
+      length = temp;
+    } else if (temp == 0x81) {
+      length = GsmPDUHelper.readHexOctet();
+      hlen++;
+      if (length < 0x80) {
+        RIL.sendStkTerminalResponse({
+          commandNumber: 0,
+          typeOfCommand: 0,
+          commandQualifier: 0,
+          resultCode: STK_RESULT_CMD_DATA_NOT_UNDERSTOOD});
+        throw new Error("Invalid length in Comprehension TLV :" + length);
+      }
+    } else if (temp == 0x82) {
+      length = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
+      hlen += 2;
+      if (lenth < 0x0100) {
+         RIL.sendStkTerminalResponse({
+          commandNumber: 0,
+          typeOfCommand: 0,
+          commandQualifier: 0,
+          resultCode: STK_RESULT_CMD_DATA_NOT_UNDERSTOOD});
+        throw new Error("Invalid length in 3-byte Comprehension TLV :" + length);
+      }
+    } else if (temp == 0x83) {
+      length = (GsmPDUHelper.readHexOctet() << 16) |
+               (GsmPDUHelper.readHexOctet() << 8)  |
+                GsmPDUHelper.readHexOctet();
+      hlen += 3;
+      if (length < 0x010000) {
+        RIL.sendStkTerminalResponse({
+          commandNumber: 0,
+          typeOfCommand: 0,
+          commandQualifier: 0,
+          resultCode: STK_RESULT_CMD_DATA_NOT_UNDERSTOOD});
+        throw new Error("Invalid length in 4-byte Comprehension TLV :" + length);
+      }
+    } else {
+      RIL.sendStkTerminalResponse({
+        commandNumber: 0,
+        typeOfCommand: 0,
+        commandQualifier: 0,
+        resultCode: STK_RESULT_CMD_DATA_NOT_UNDERSTOOD});
+      throw new Error("Invalid octet in Comprehension TLV :" + length);
+    }
+
+    let ctlv = {
+      tag: tag,
+      length: length,
+      value: StkProactiveCmdHelper.retrieve(tag, length),
+      cr: cr,
+      hlen: hlen
+    };
+    return ctlv;
+  },
+
+  decodeChunks: function decodeChunks(length) {
+    let chunks = [];
+    let index = 0;
+    while (index < length) {
+      let tlv = this.decode();
+      chunks.push(tlv);
+      index += tlv.length;
+      index += tlv.hlen;
+    }
+    return chunks;
+  }
+};
+
+let BerTlvHelper = {
+  /**
+   * Decode Ber TLV.
+   *
+   * @param dataLen
+   *        The length of data in bytes.
+   */
+  decode: function decode(dataLen) {
+    // See TS 11.14, Annex D for BerTlv.
+    let hlen = 0;
+    let tag = GsmPDUHelper.readHexOctet();
+    hlen++;
+
+    // Length    | Byte 1                | Byte 2
+    // 0 - 127   | length ('00' to '7f') | N/A
+    // 128 - 255 | '81'                  | length ('80' to 'ff')
+    let length;
+    if (tag == BER_PROACTIVE_COMMAND_TAG) {
+      let temp = GsmPDUHelper.readHexOctet();
+      hlen++;
+      if (temp < 0x80) {
+        length = temp;
+      } else if(temp == 0x81) {
+        length = GsmPDUHelper.readHexOctet();
+        if (length < 0x80) {
+          RIL.sendStkTerminalResponse({
+            commandNumber: 0,
+            typeOfCommand: 0,
+            commandQualifier: 0,
+            resultCode: STK_RESULT_CMD_DATA_NOT_UNDERSTOOD});
+          throw new Error("Invalid length " + length);
+        }
+      } else {
+        RIL.sendStkTerminalResponse({
+          commandNumber: 0,
+          typeOfCommand: 0,
+          commandQualifier: 0,
+          resultCode: STK_RESULT_CMD_DATA_NOT_UNDERSTOOD});
+        throw new Error("Invalid length octet " + temp);
+      }
+    } else {
+      RIL.sendStkTerminalResponse({
+        commandNumber: 0,
+        typeOfCommand: 0,
+        commandQualifier: 0,
+        resultCode: STK_RESULT_CMD_DATA_NOT_UNDERSTOOD});
+      throw new Error("Unknown BER tag");
+    }
+
+    // If the value length of the BerTlv is larger than remaining value on Parcel.
+    if (dataLen - hlen < length) {
+      RIL.sendStkTerminalResponse({
+        commandNumber: 0,
+        typeOfCommand: 0,
+        commandQualifier: 0,
+        resultCode: STK_RESULT_CMD_DATA_NOT_UNDERSTOOD});
+      throw new Error("BerTlvHelper value length too long!!");
+      return;
+    }
+
+    let ctlvs = ComprehensionTlvHelper.decodeChunks(length);
+    let berTlv = {
+      tag: tag,
+      length: length,
+      value: ctlvs
+    };
+    return berTlv;
   }
 };
 
 /**
  * Global stuff.
  */
 
 if (!this.debug) {