Bug 713426 - Support PDP data call in ril_worker. r=philikon
authorThinker Li <tlee@mozilla.com>
Tue, 17 Jan 2012 17:34:09 -0800
changeset 85991 b105fa66bb3707858f9ab67cfbf40e0c41c51fbf
parent 85990 3b8dd350dbab3c8f6322920a2a50704a2ce04a13
child 85992 924d6091bec7b8d8a9974d020cf09d52129487c4
push id805
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 18:17:35 +0000
treeherdermozilla-aurora@6fb3bf232436 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersphilikon
bugs713426
milestone12.0a1
Bug 713426 - Support PDP data call in ril_worker. r=philikon
dom/telephony/nsITelephone.idl
dom/telephony/nsTelephonyWorker.js
dom/telephony/ril_consts.js
dom/telephony/ril_worker.js
--- a/dom/telephony/nsITelephone.idl
+++ b/dom/telephony/nsITelephone.idl
@@ -48,32 +48,63 @@ interface nsITelephoneCallback : nsISupp
   // 'callState' uses the CALL_STATE values from nsITelephone. Return true to
   // continue enumeration or false to cancel.
   boolean enumerateCallState(in unsigned long callIndex,
                              in unsigned short callState,
                              in AString number,
                              in boolean isActive);
 };
 
-[scriptable, uuid(5be6e41d-3aee-4f5c-8284-95cf529dd6fe)]
+[scriptable, uuid(8399fddd-471c-41ac-8f35-99f7dbb738ec)]
+interface nsIDataCallInfo : nsISupports
+{
+  readonly attribute unsigned long callState;
+  readonly attribute AString cid;
+  readonly attribute AString apn;
+};
+
+[scriptable, uuid(36cc4b89-0338-4ff7-a3c2-d78e60f2ea98)]
+interface nsIPhoneDataCallCallback : nsISupports
+{
+  /**
+   * This method is called when the state of a data call is changed.
+   *
+   * @param dataState use DATACALL_STATE_* values from nsITelephone.
+   */
+  void dataCallStateChanged(in AString cid,
+                            in AString interfaceName,
+                            in unsigned short callState);
+
+  void receiveDataCallList([array,size_is(aLength)] in nsIDataCallInfo aDataCalls,
+                           in unsigned long aLength);
+};
+
+[scriptable, uuid(78ed0beb-d6ad-42f8-929a-8d003285784f)]
 interface nsITelephone : nsISupports
 {
   const unsigned short CALL_STATE_UNKNOWN = 0;
   const unsigned short CALL_STATE_DIALING = 1;
   const unsigned short CALL_STATE_RINGING = 2;
   const unsigned short CALL_STATE_BUSY = 3;
   const unsigned short CALL_STATE_CONNECTING = 4;
   const unsigned short CALL_STATE_CONNECTED = 5;
   const unsigned short CALL_STATE_HOLDING = 6;
   const unsigned short CALL_STATE_HELD = 7;
   const unsigned short CALL_STATE_RESUMING = 8;
   const unsigned short CALL_STATE_DISCONNECTING = 9;
   const unsigned short CALL_STATE_DISCONNECTED = 10;
   const unsigned short CALL_STATE_INCOMING = 11;
 
+  // Keep consistent with GECKO_DATACALL_STATE_* values in ril_consts.js
+  const unsigned short DATACALL_STATE_UNKNOWN = 0;
+  const unsigned short DATACALL_STATE_CONNECTING = 1;
+  const unsigned short DATACALL_STATE_CONNECTED = 2;
+  const unsigned short DATACALL_STATE_DISCONNECTING = 3;
+  const unsigned short DATACALL_STATE_DISCONNECTED = 4;
+
   readonly attribute jsval currentState;
 
   void registerCallback(in nsITelephoneCallback callback);
   void unregisterCallback(in nsITelephoneCallback callback);
 
   /**
    * Will continue calling callback.enumerateCallState until the callback
    * returns false.
@@ -90,14 +121,28 @@ interface nsITelephone : nsISupports
   void stopTone();
 
   void answerCall(in unsigned long callIndex);
   void rejectCall(in unsigned long callIndex);
 
   attribute bool microphoneMuted;
   attribute bool speakerEnabled;
 
+  // PDP APIs
+  void setupDataCall(in long radioTech,
+                     in DOMString apn,
+                     in DOMString user,
+                     in DOMString passwd,
+                     in long chappap,
+                     in DOMString pdptype);
+  void deactivateDataCall(in DOMString cid,
+                          in DOMString reason);
+  void getDataCallList();
+  
+  void registerDataCallCallback(in nsIPhoneDataCallCallback callback);
+  void unregisterDataCallCallback(in nsIPhoneDataCallCallback callback);
+
   /**
    * SMS-related functionality.
    */
   unsigned short getNumberOfMessagesForText(in DOMString text);
   void sendSMS(in DOMString number, in DOMString message);
 };
--- a/dom/telephony/nsTelephonyWorker.js
+++ b/dom/telephony/nsTelephonyWorker.js
@@ -45,16 +45,18 @@ Cu.import("resource://gre/modules/Servic
 
 var RIL = {};
 Cu.import("resource://gre/modules/ril_consts.js", RIL);
 
 const DEBUG = true; // set to false to suppress debug messages
 
 const TELEPHONYWORKER_CID =
   Components.ID("{2d831c8d-6017-435b-a80c-e5d422810cea}");
+const DATACALLINFO_CID =
+  Components.ID("{ef474cd9-94f7-4c05-a31b-29b9de8a10d2}");
 
 const nsIAudioManager = Ci.nsIAudioManager;
 const nsITelephone = Ci.nsITelephone;
 
 const kSmsReceivedObserverTopic          = "sms-received";
 const DOM_SMS_DELIVERY_RECEIVED          = "received";
 
 XPCOMUtils.defineLazyServiceGetter(this, "gSmsService",
@@ -105,16 +107,30 @@ XPCOMUtils.defineLazyGetter(this, "gAudi
   } catch (ex) {
     //TODO on the phone this should not fall back as silently.
     debug("Using fake audio manager.");
     return FakeAudioManager;
   }
 });
 
 
+function DataCallInfo(state, cid, apn) {
+  this.callState = state;
+  this.cid = cid;
+  this.apn = apn;
+}
+DataCallInfo.protoptype = {
+  classID:      DATACALLINFO_CID,
+  classInfo:    XPCOMUtils.generateCI({classID: DATACALLINFO_CID,
+                                       classDescription: "DataCallInfo",
+                                       interfaces: [Ci.nsIDataCallInfo]}),
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIDataCallInfo]),
+};
+
+
 function nsTelephonyWorker() {
   this.worker = new ChromeWorker("resource://gre/modules/ril_worker.js");
   this.worker.onerror = this.onerror.bind(this);
   this.worker.onmessage = this.onmessage.bind(this);
   debug("Starting Worker\n");
   this.currentState = {
     signalStrength: null,
     operator:       null,
@@ -173,16 +189,22 @@ nsTelephonyWorker.prototype = {
         this.currentState.radioState = message.radioState;
         break;
       case "cardstatechange":
         this.currentState.cardState = message.cardState;
         break;
       case "sms-received":
         this.handleSmsReceived(message);
         return;
+      case "datacallstatechange":
+        this.handleDataCallState(message);
+        break;
+      case "datacalllist":
+        this.handleDataCallList(message);
+        break;
       default:
         throw new Error("Don't know about this message type: " + message.type);
     }
   },
 
   /**
    * Track the active call and update the audio system as its state changes.
    *
@@ -273,16 +295,39 @@ nsTelephonyWorker.prototype = {
                                            DOM_SMS_DELIVERY_RECEIVED,
                                            message.sender || null,
                                            message.receiver || null,
                                            message.body || null,
                                            message.timestamp);
     Services.obs.notifyObservers(sms, kSmsReceivedObserverTopic, null);
   },
 
+  /**
+   * Handle data call state changes.
+   */
+  handleDataCallState: function handleDataCallState(message) {
+    let ifname = message.ifname ? message.ifname : "";
+    this._deliverDataCallCallback("dataCallStateChanged",
+                                  [message.cid, ifname, message.state]);
+  },
+
+  /**
+   * Handle data call list.
+   */
+  handleDataCallList: function handleDataCallList(message) {
+    let datacalls = [];
+    for each (let datacall in message.datacalls) {
+      datacalls.push(new DataCallInfo(datacall.state,
+                                      datacall.cid,
+                                      datacall.apn));
+    }
+    this._deliverDataCallCallback("receiveDataCallList",
+                                  [datacalls, datacalls.length]);
+  },
+
   // nsIRadioWorker
 
   worker: null,
 
   // nsITelephone
 
   currentState: null,
 
@@ -290,17 +335,17 @@ nsTelephonyWorker.prototype = {
     debug("Dialing " + number);
     this.worker.postMessage({type: "dial", number: number});
   },
 
   hangUp: function hangUp(callIndex) {
     debug("Hanging up call no. " + callIndex);
     this.worker.postMessage({type: "hangUp", callIndex: callIndex});
   },
-  
+
   startTone: function startTone(dtmfChar) {
     debug("Sending Tone for " + dtmfChar);
     this.worker.postMessage({type: "startTone", dtmfChar: dtmfChar});
   },
 
   stopTone: function stopTone() {
     debug("Stopping Tone");
     this.worker.postMessage({type: "stopTone"});
@@ -410,16 +455,94 @@ nsTelephonyWorker.prototype = {
       }
       try {
         handler.apply(callback, args);
       } catch (e) {
         debug("callback handler for " + name + " threw an exception: " + e);
       }
     }
   },
+
+  registerDataCallCallback: function registerDataCallCallback(callback) {
+    if (this._datacall_callbacks) {
+      if (this._datacall_callbacks.indexOf(callback) != -1) {
+        throw new Error("Already registered this callback!");
+      }
+    } else {
+      this._datacall_callbacks = [];
+    }
+    this._datacall_callbacks.push(callback);
+    debug("Registering callback: " + callback);
+  },
+
+  unregisterDataCallCallback: function unregisterDataCallCallback(callback) {
+    if (!this._datacall_callbacks) {
+      return;
+    }
+    let index = this._datacall_callbacks.indexOf(callback);
+    if (index != -1) {
+      this._datacall_callbacks.splice(index, 1);
+      debug("Unregistering callback: " + callback);
+    }
+  },
+
+  _deliverDataCallCallback: function _deliverDataCallCallback(name, args) {
+    // We need to worry about callback registration state mutations during the
+    // callback firing. The behaviour we want is to *not* call any callbacks
+    // that are added during the firing and to *not* call any callbacks that are
+    // removed during the firing. To address this, we make a copy of the
+    // callback list before dispatching and then double-check that each callback
+    // is still registered before calling it.
+    if (!this._datacall_callbacks) {
+      return;
+    }
+    let callbacks = this._datacall_callbacks.slice();
+    for each (let callback in callbacks) {
+      if (this._datacall_callbacks.indexOf(callback) == -1) {
+        continue;
+      }
+      let handler = callback[name];
+      if (typeof handler != "function") {
+        throw new Error("No handler for " + name);
+      }
+      try {
+        handler.apply(callback, args);
+      } catch (e) {
+        debug("callback handler for " + name + " threw an exception: " + e);
+      }
+    }
+  },
+
+  setupDataCall: function(radioTech, apn, user, passwd, chappap, pdptype) {
+    this.worker.postMessage({type: "setupDataCall",
+                             radioTech: radioTech,
+                             apn: apn,
+                             user: user,
+                             passwd: passwd,
+                             chappap: chappap,
+                             pdptype: pdptype});
+    this._deliverDataCallCallback("dataCallStateChanged",
+                                  [message.cid, "",
+                                   RIL.GECKO_DATACALL_STATE_CONNECTING]);
+  },
+
+  deactivateDataCall: function(cid, reason) {
+    this.worker.postMessage({type: "deactivateDataCall",
+                             cid: cid,
+                             reason: reason});
+    this._deliverDataCallCallback("dataCallStateChanged",
+                                  [message.cid,
+                                   "",
+                                   RIL.GECKO_DATACALL_STATE_DISCONNECTING]);
+  },
+
+  getDataCallList: function getDataCallList() {
+    this.worker.postMessage({type: "getDataCallList"});
+  },
+
 };
 
 const NSGetFactory = XPCOMUtils.generateNSGetFactory([nsTelephonyWorker]);
 
 let debug;
 if (DEBUG) {
   debug = function (s) {
     dump("-*- TelephonyWorker component: " + s + "\n");
--- a/dom/telephony/ril_consts.js
+++ b/dom/telephony/ril_consts.js
@@ -402,11 +402,37 @@ const PDU_ALPHABET_7BIT_DEFAULT = [
   "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
   "\xe4",   // LATIN SMALL LETTER A WITH DIAERESIS
   "\xf6",   // LATIN SMALL LETTER O WITH DIAERESIS
   "\xf1",   // LATIN SMALL LETTER N WITH TILDE
   "\xfc",   // LATIN SMALL LETTER U WITH DIAERESIS
   "\xe0"    // LATIN SMALL LETTER A WITH GRAVE
 ];
 
+const DATACALL_RADIOTECHONLOGY_CDMA = 0;
+const DATACALL_RADIOTECHONLOGY_GSM = 1;
+
+const DATACALL_AUTH_NONE = 0;
+const DATACALL_AUTH_PAP = 1;
+const DATACALL_AUTH_CHAP = 2;
+const DATACALL_AUTH_PAP_OR_CHAP = 3;
+
+const DATACALL_PROFILE_DEFAULT = 0;
+const DATACALL_PROFILE_TETHERED = 1;
+const DATACALL_PROFILE_OEM_BASE = 1000;
+
+const DATACALL_DEACTIVATE_NO_REASON = 0;
+const DATACALL_DEACTIVATE_RADIO_SHUTDOWN = 1;
+
+const DATACALL_INACTIVE = 0;
+const DATACALL_ACTIVE_DOWN = 1;
+const DATACALL_ACTIVE_UP = 2;
+
+// Keep consistent with nsITelephone.DATACALL_STATE_*.
+const GECKO_DATACALL_STATE_UNKNOWN = 0;
+const GECKO_DATACALL_STATE_CONNECTING = 1;
+const GECKO_DATACALL_STATE_CONNECTED = 2;
+const GECKO_DATACALL_STATE_DISCONNECTING = 3;
+const GECKO_DATACALL_STATE_DISCONNECTED = 4;
+
 
 // Allow this file to be imported via Components.utils.import().
 const EXPORTED_SYMBOLS = Object.keys(this);
--- a/dom/telephony/ril_worker.js
+++ b/dom/telephony/ril_worker.js
@@ -111,16 +111,19 @@ let Buf = {
     this.currentParcelSize = 0;
 
     // This gets incremented each time we send out a parcel.
     this.token = 1;
 
     // Maps tokens we send out with requests to the request type, so that
     // when we get a response parcel back, we know what request it was for.
     this.tokenRequestMap = {};
+
+    // This is the token of last solicited response.
+    this.lastSolicitedToken = 0;
   },
 
   /**
    * Grow the incoming buffer.
    *
    * @param min_size
    *        Minimum new size. The actual new size will be the the smallest
    *        power of 2 that's larger than this number.
@@ -444,16 +447,17 @@ let Buf = {
         //TODO
         debug("Received error " + error + " for solicited parcel type " +
               request_type);
         return;
       }
       debug("Solicited response for request type " + request_type +
             ", token " + token);
       delete this.tokenRequestMap[token];
+      this.lastSolicitedToken = token;
     } else if (response_type == RESPONSE_TYPE_UNSOLICITED) {
       request_type = this.readUint32();
       length -= UINT32_SIZE;
       debug("Unsolicited response for request type " + request_type);
     } else {
       debug("Unknown response type: " + response_type);
       return;
     }
@@ -745,17 +749,16 @@ let RIL = {
   },
 
   /**
    * Start a DTMF Tone.
    *
    * @param dtmfChar
    *        DTMF signal to send, 0-9, *, +
    */
-
   startTone: function startTone(dtmfChar) {
     Buf.newParcel(REQUEST_DTMF_START);
     Buf.writeString(dtmfChar);
     Buf.sendParcel();
   },
 
   stopTone: function stopTone() {
     Buf.simpleRequest(REQUEST_DTMF_STOP);
@@ -775,21 +778,88 @@ let RIL = {
   },
 
   /**
    * Set the Short Message Service Center address.
    *
    * @param smsc
    *        Short Message Service Center address in PDU format.
    */
-   setSMSCAddress: function setSMSCAddress(smsc) {
-     Buf.newParcel(REQUEST_SET_SMSC_ADDRESS);
-     Buf.writeString(smsc);
-     Buf.sendParcel();
-   },
+  setSMSCAddress: function setSMSCAddress(smsc) {
+    Buf.newParcel(REQUEST_SET_SMSC_ADDRESS);
+    Buf.writeString(smsc);
+    Buf.sendParcel();
+  },
+
+  /**
+   * Setup a data call.
+   *
+   * @param radioTech
+   *        Integer to indicate radio technology.
+   *        DATACALL_RADIOTECHONLOGY_CDMA => CDMA.
+   *        DATACALL_RADIOTECHONLOGY_GSM  => GSM.
+   * @param apn
+   *        String containing the name of the APN to connect to.
+   * @param user
+   *        String containing the username for the APN.
+   * @param passwd
+   *        String containing the password for the APN.
+   * @param chappap
+   *        Integer containing CHAP/PAP auth type.
+   *        DATACALL_AUTH_NONE        => PAP and CHAP is never performed.
+   *        DATACALL_AUTH_PAP         => PAP may be performed.
+   *        DATACALL_AUTH_CHAP        => CHAP may be performed.
+   *        DATACALL_AUTH_PAP_OR_CHAP => PAP / CHAP may be performed.
+   * @param pdptype
+   *        String containing PDP type to request. ("IP", "IPV6", ...)
+   */
+  setupDataCall: function (radioTech, apn, user, passwd, chappap, pdptype) {
+    let token = Buf.newParcel(REQUEST_SETUP_DATA_CALL);
+    Buf.writeUint32(7);
+    Buf.writeString(radioTech.toString());
+    Buf.writeString(DATACALL_PROFILE_DEFAULT.toString());
+    Buf.writeString(apn);
+    Buf.writeString(user);
+    Buf.writeString(passwd);
+    Buf.writeString(chappap.toString());
+    Buf.writeString(pdptype);
+    Buf.sendParcel();
+    return token;
+  },
+
+  /**
+   * Deactivate a data call.
+   *
+   * @param cid
+   *        String containing CID.
+   * @param reason
+   *        One of DATACALL_DEACTIVATE_* constants.
+   */
+  deactivateDataCall: function (cid, reason) {
+    let token = Buf.newParcel(REQUEST_DEACTIVATE_DATA_CALL);
+    Buf.writeUint32(2);
+    Buf.writeString(cid);
+    Buf.writeString(reason);
+    Buf.sendParcel();
+    return token;
+  },
+
+  /**
+   * Get a list of data calls.
+   */
+  getDataCallList: function getDataCallList() {
+    Buf.simpleRequest(REQUEST_DATA_CALL_LIST);
+  },
+
+  /**
+   * Get failure casue code for the most recently failed PDP context.
+   */
+  getFailCauseCode: function getFailCauseCode() {
+    Buf.simpleRequest(REQUEST_LAST_CALL_FAIL_CAUSE);
+  },
 
   /**
    * Handle incoming requests from the RIL. We find the method that
    * corresponds to the request type. Incidentally, the request type
    * _is_ the method name, so that's easy.
    */
 
   handleParcel: function handleParcel(request_type, length) {
@@ -944,17 +1014,20 @@ RIL[REQUEST_DTMF] = function REQUEST_DTM
 };
 RIL[REQUEST_SEND_SMS] = function REQUEST_SEND_SMS() {
   let messageRef = Buf.readUint32();
   let ackPDU = Buf.readString();
   let errorCode = Buf.readUint32();
   Phone.onSendSMS(messageRef, ackPDU, errorCode);
 };
 RIL[REQUEST_SEND_SMS_EXPECT_MORE] = null;
-RIL[REQUEST_SETUP_DATA_CALL] = null;
+RIL[REQUEST_SETUP_DATA_CALL] = function REQUEST_SETUP_DATA_CALL() {
+  let [cid, ifname, ipaddr, dns, gw] = Buf.readStringList();
+  Phone.onSetupDataCall(Buf.lastSolicitedToken, cid, ifname, ipaddr, dns, gw);
+};
 RIL[REQUEST_SIM_IO] = null;
 RIL[REQUEST_SEND_USSD] = null;
 RIL[REQUEST_CANCEL_USSD] = null;
 RIL[REQUEST_GET_CLIR] = null;
 RIL[REQUEST_SET_CLIR] = null;
 RIL[REQUEST_QUERY_CALL_FORWARD_STATUS] = null;
 RIL[REQUEST_SET_CALL_FORWARD] = null;
 RIL[REQUEST_QUERY_CALL_WAITING] = null;
@@ -968,17 +1041,19 @@ RIL[REQUEST_GET_IMEI] = function REQUEST
 };
 RIL[REQUEST_GET_IMEISV] = function REQUEST_GET_IMEISV() {
   let imeiSV = Buf.readString();
   Phone.onIMEISV(imeiSV);
 };
 RIL[REQUEST_ANSWER] = function REQUEST_ANSWER(length) {
   Phone.onAnswerCall();
 };
-RIL[REQUEST_DEACTIVATE_DATA_CALL] = null;
+RIL[REQUEST_DEACTIVATE_DATA_CALL] = function REQUEST_DEACTIVATE_DATA_CALL() {
+  Phone.onDeactivateDataCall(Buf.lastSolicitedToken);
+};
 RIL[REQUEST_QUERY_FACILITY_LOCK] = null;
 RIL[REQUEST_SET_FACILITY_LOCK] = null;
 RIL[REQUEST_CHANGE_BARRING_PASSWORD] = null;
 RIL[REQUEST_QUERY_NETWORK_SELECTION_MODE] = function REQUEST_QUERY_NETWORK_SELECTION_MODE() {
   let response = Buf.readUint32List();
   Phone.onNetworkSelectionMode(response);
 };
 RIL[REQUEST_SET_NETWORK_SELECTION_AUTOMATIC] = null;
@@ -988,25 +1063,44 @@ RIL[REQUEST_DTMF_START] = function REQUE
   Phone.onStartTone();
 };
 RIL[REQUEST_DTMF_STOP] = function REQUEST_DTMF_STOP() {
   Phone.onStopTone();
 };
 RIL[REQUEST_BASEBAND_VERSION] = function REQUEST_BASEBAND_VERSION() {
   let version = Buf.readString();
   Phone.onBasebandVersion(version);
-},
+};
 RIL[REQUEST_SEPARATE_CONNECTION] = null;
 RIL[REQUEST_SET_MUTE] = function REQUEST_SET_MUTE(length) {
   Phone.onSetMute();
 };
 RIL[REQUEST_GET_MUTE] = null;
 RIL[REQUEST_QUERY_CLIP] = null;
 RIL[REQUEST_LAST_DATA_CALL_FAIL_CAUSE] = null;
-RIL[REQUEST_DATA_CALL_LIST] = null;
+RIL[REQUEST_DATA_CALL_LIST] = function REQUEST_DATA_CALL_LIST(length) {
+  let datacalls = [];
+
+  if (!length) {
+    return;
+  }
+
+  let num = Buf.readUint32();
+  for (let i = 0; i < num; i++) {
+    datacalls.push({
+      cid: Buf.readUint32().toString(),
+      active: Buf.readUint32(),
+      type: Buf.readString(),
+      apn: Buf.readString(),
+      address: Buf.readString()
+    });
+  }
+
+  Phone.onDataCallList(datacalls);
+};
 RIL[REQUEST_RESET_RADIO] = null;
 RIL[REQUEST_OEM_HOOK_RAW] = null;
 RIL[REQUEST_OEM_HOOK_STRINGS] = null;
 RIL[REQUEST_SCREEN_STATE] = null;
 RIL[REQUEST_SET_SUPP_SVC_NOTIFICATION] = null;
 RIL[REQUEST_WRITE_SMS_TO_SIM] = null;
 RIL[REQUEST_DELETE_SMS_ON_SIM] = null;
 RIL[REQUEST_SET_BAND_MODE] = null;
@@ -1171,16 +1265,26 @@ let Phone = {
    */
   currentCalls: {},
 
   /**
    * Mute or unmute the radio.
    */
   _muted: true,
 
+  /**
+   * Existing data calls.
+   */
+  currentDataCalls: {},
+
+  /**
+   * Tracks active requests to the RIL concerning 3G data calls.
+   */
+  activeDataRequests: {},
+
   get muted() {
     return this._muted;
   },
 
   set muted(val) {
     val = Boolean(val);
     if (this._muted != val) {
       RIL.setMute(val);
@@ -1578,16 +1682,98 @@ let Phone = {
 
   onNewSMSOnSIM: function onNewSMSOnSIM(info) {
     //TODO
   },
 
   onAcknowledgeSMS: function onAcknowledgeSMS() {
   },
 
+  onSetupDataCall: function onSetupDataCall(token, cid, ifname, ipaddr,
+                                            dns, gw) {
+    let options = this.activeDataRequests[token];
+    delete this.activeDataRequests[token];
+
+    this.currentDataCalls[cid] = {
+      state: GECKO_DATACALL_STATE_CONNECTED,
+      cid: cid,
+      apn: options.apn,
+      ifname: ifname,
+      ipaddr: ipaddr,
+      dns: dns,
+      gw: gw,
+    };
+    this.sendDOMMessage({type: "datacallstatechange",
+                         state: GECKO_DATACALL_STATE_CONNECTED,
+                         cid: cid,
+                         apn: options.apn,
+                         ifname: ifname,
+                         ipaddr: ipaddr,
+                         dns: dns,
+                         gateway: gw});
+  },
+
+  onDeactivateDataCall: function onDeactivateDataCall(token) {
+    let options = this.activeDataRequests[token];
+    delete this.activeDataRequests[token];
+
+    let cid = options.cid;
+    if (!(cid in this.currentDataCalls)) {
+      return;
+    }
+
+    let apn = this.currentDataCalls[cid].apn;
+    delete this.currentDataCalls[cid];
+    this.sendDOMMessage({type: "datacallstatechange",
+                         state: GECKO_DATACALL_STATE_DISCONNECTED,
+                         cid: cid,
+                         apn: apn});
+  },
+
+  onDataCallList: function onDataCallList(datacalls) {
+    let currentDataCalls = this.currentDataCalls;
+
+    // Sync content of currentDataCalls and data call list.
+    for each (let datacall in datacalls) {
+      let {cid, apn} = datacall;
+
+      if (datacall.active != DATACALL_INACTIVE) {
+        // XXX: This should be followed up.
+        // datacall.active == DATACALL_ACTIVE_DOWN(1) for my device
+        if (!(cid in currentDataCalls)) {
+          let datacall = {state: GECKO_DATACALL_STATE_CONNECTED,
+                          cid: cid,
+                          apn: apn,
+                          ipaddr: datacall.address};
+          currentDataCalls[cid] = datacall;
+
+          this.sendDOMMessage({type: "datacallstatechange",
+                               state: GECKO_DATACALL_STATE_CONNECTED,
+                               cid: cid,
+                               apn: apn});
+        }
+      } else {                 // datacall.active == DATACALL_INACTIVE
+        if (cid in currentDataCalls) {
+          delete currentDataCalls[cid];
+          this.sendDOMMessage({type: "datacallstatechange",
+                               state: GECKO_DATACALL_STATE_DISCONNECTED,
+                               cid: cid,
+                               apn: apn});
+        }
+      }
+    }
+
+    let datacall_list = [];
+    for each (let datacall in this.currentDataCalls) {
+      datacall_list.push(datacall);
+    }
+    this.sendDOMMessage({type: "datacalllist",
+                         datacalls: datacall_list});
+  },
+
   /**
    * Outgoing requests to the RIL. These can be triggered from the
    * main thread via messages that look like this:
    *
    *   {type:  "methodName",
    *    extra: "parameters",
    *    go:    "here"}
    *
@@ -1725,16 +1911,64 @@ let Phone = {
     //TODO: the data encoding and length in octets should eventually be
     // computed on the mainthread and passed down to us.
     RIL.sendSMS(this.SMSC, options.number, options.body,
                 PDU_DCS_MSG_CODING_7BITS_ALPHABET, //TODO: hard-coded for now,
                 Math.ceil(options.body.length * 7 / 8)); //TODO: ditto
   },
 
   /**
+   * Setup a data call (PDP).
+   */
+  setupDataCall: function setupDataCall(options) {
+    if (DEBUG) debug("setupDataCall: " + JSON.stringify(options));
+
+    let token = RIL.setupDataCall(options.radioTech, options.apn,
+                                  options.user, options.passwd,
+                                  options.chappap, options.reason);
+    this.activeDataRequests[token] = options;
+    this.sendDOMMessage({type: "datacallstatechange",
+                         state: GECKO_DATACALL_STATE_CONNECTING,
+                         apn: options.apn});
+  },
+
+  /**
+   * Deactivate a data call (PDP).
+   */
+  deactivateDataCall: function deactivateDataCall(options) {
+    if (!(options.cid in this.currentDataCalls)) {
+      return;
+    }
+
+    let datacall = this.currentDataCalls[options.cid];
+    datacall.state = GECKO_DATACALL_STATE_DISCONNECTING;
+
+    let token = RIL.deactivateDataCall(options.cid, options.reason);
+    this.activeDataRequests[token] = options;
+    this.sendDOMMessage({type: "datacallstatechange",
+                         state: GECKO_DATACALL_STATE_DISCONNECTING,
+                         cid: options.cid,
+                         apn: datacall.apn});
+  },
+
+  /**
+   * Get the list of data calls.
+   */
+  getDataCallList: function getDataCallList(options) {
+    RIL.getDataCallList();
+  },
+
+  /**
+   * Get failure cause code for the last failed PDP context.
+   */
+  getFailCauseCode: function getFailCauseCode(options) {
+    RIL.getFailCauseCode();
+  },
+
+  /**
    * Handle incoming messages from the main UI thread.
    *
    * @param message
    *        Object containing the message. Messages are supposed
    */
   handleDOMMessage: function handleMessage(message) {
     if (DEBUG) debug("Received DOM message " + JSON.stringify(message));
     let method = this[message.type];