Bug 729173 - Part 5 - Expose the RIL state to content. r=fabrice,qDot
authorPhilipp von Weitershausen <philipp@weitershausen.de>
Thu, 19 Apr 2012 18:33:25 -0300
changeset 95318 ee7f4d1d2934a7e041736240cbb89d70a2dbb166
parent 95317 466592cf04888fd518815a7436111d2567cfdd33
child 95319 880879bfc6a808540620195dc6208fb955f1294b
push id886
push userlsblakk@mozilla.com
push dateMon, 04 Jun 2012 19:57:52 +0000
treeherdermozilla-beta@bbd8d5efd6d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfabrice, qDot
bugs729173
milestone14.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 729173 - Part 5 - Expose the RIL state to content. r=fabrice,qDot
b2g/installer/package-manifest.in
browser/installer/package-manifest.in
dom/system/gonk/Makefile.in
dom/system/gonk/RILContentHelper.js
dom/system/gonk/RadioInterfaceLayer.js
dom/system/gonk/RadioInterfaceLayer.manifest
dom/system/gonk/nsIRadioInterfaceLayer.idl
dom/system/gonk/nsRadioInterfaceLayer.h
dom/system/gonk/ril_worker.js
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -408,16 +408,17 @@
 @BINPATH@/components/messageWakeupService.manifest
 @BINPATH@/components/SettingsManager.js
 @BINPATH@/components/SettingsManager.manifest
 @BINPATH@/components/nsFilePicker.js
 @BINPATH@/components/nsFilePicker.manifest
 #ifdef MOZ_B2G_RIL
 @BINPATH@/components/RadioInterfaceLayer.manifest
 @BINPATH@/components/RadioInterfaceLayer.js
+@BINPATH@/components/RILContentHelper.js
 @BINPATH@/components/SmsDatabaseService.manifest
 @BINPATH@/components/SmsDatabaseService.js
 @BINPATH@/components/WifiWorker.js
 @BINPATH@/components/WifiWorker.manifest
 @BINPATH@/components/DOMWifiManager.js
 @BINPATH@/components/DOMWifiManager.manifest
 #endif
 #ifdef XP_MACOSX
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -395,16 +395,17 @@
 @BINPATH@/components/nsInputListAutoComplete.js
 @BINPATH@/components/contentSecurityPolicy.manifest
 @BINPATH@/components/contentSecurityPolicy.js
 @BINPATH@/components/contentAreaDropListener.manifest
 @BINPATH@/components/contentAreaDropListener.js
 #ifdef MOZ_B2G_RIL
 @BINPATH@/components/RadioInterfaceLayer.manifest
 @BINPATH@/components/RadioInterfaceLayer.js
+@BINPATH@/components/RILContentHelper.js
 @BINPATH@/components/SmsDatabaseService.manifest
 @BINPATH@/components/SmsDatabaseService.js
 @BINPATH@/components/WifiWorker.js
 @BINPATH@/components/WifiWorker.manifest
 @BINPATH@/components/DOMWifiManager.js
 @BINPATH@/components/DOMWifiManager.manifest
 #endif
 @BINPATH@/components/BrowserProfileMigrators.manifest
--- a/dom/system/gonk/Makefile.in
+++ b/dom/system/gonk/Makefile.in
@@ -44,16 +44,17 @@ CPPSRCS += \
   $(NULL)
 # for our local copy of AudioSystem.h
 LOCAL_INCLUDES += -I$(topsrcdir)/media/libsydneyaudio/src
 endif
 
 EXTRA_COMPONENTS = \
   RadioInterfaceLayer.manifest \
   RadioInterfaceLayer.js \
+  RILContentHelper.js \
   $(NULL)
 
 EXTRA_JS_MODULES = \
   net_worker.js \
   ril_consts.js \
   ril_worker.js \
   systemlibs.js \
   $(NULL)
new file mode 100644
--- /dev/null
+++ b/dom/system/gonk/RILContentHelper.js
@@ -0,0 +1,153 @@
+/* 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/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+var RIL = {};
+Cu.import("resource://gre/modules/ril_consts.js", RIL);
+
+const DEBUG = false; // set to true to see debug messages
+
+const RILCONTENTHELPER_CID =
+  Components.ID("{472816e1-1fd6-4405-996c-806f9ea68174}");
+const MOBILECONNECTIONINFO_CID =
+  Components.ID("{a35cfd39-2d93-4489-ac7d-396475dacb27}");
+
+const RIL_IPC_MSG_NAMES = [
+  "RIL:CardStateChanged",
+  "RIL:VoiceInfoChanged",
+  "RIL:DataInfoChanged",
+];
+
+const kVoiceChangedTopic     = "mobile-connection-voice-changed";
+const kDataChangedTopic      = "mobile-connection-data-changed";
+const kCardStateChangedTopic = "mobile-connection-cardstate-changed";
+
+XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
+                                   "@mozilla.org/childprocessmessagemanager;1",
+                                   "nsIFrameMessageManager");
+
+function MobileConnectionInfo() {}
+MobileConnectionInfo.prototype = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMMozMobileConnectionInfo]),
+  classID:        MOBILECONNECTIONINFO_CID,
+  classInfo:      XPCOMUtils.generateCI({
+    classID:          MOBILECONNECTIONINFO_CID,
+    classDescription: "MobileConnectionInfo",
+    flags:            Ci.nsIClassInfo.DOM_OBJECT,
+    interfaces:       [Ci.nsIDOMMozMobileConnectionInfo]
+  }),
+
+  // nsIDOMMozMobileConnectionInfo
+
+  connected: false,
+  emergencyCallsOnly: false,
+  roaming: false,
+  operator: null,
+  type: null,
+  signalStrength: null,
+  relSignalStrength: null
+};
+
+
+function RILContentHelper() {
+  this.voiceConnectionInfo = new MobileConnectionInfo();
+  this.dataConnectionInfo = new MobileConnectionInfo();
+
+  for each (let msgname in RIL_IPC_MSG_NAMES) {
+    cpmm.addMessageListener(msgname, this);
+  }
+  Services.obs.addObserver(this, "xpcom-shutdown", false);
+
+  // Request initial state.
+  let radioState = cpmm.QueryInterface(Ci.nsISyncMessageSender)
+                       .sendSyncMessage("RIL:GetRadioState")[0];
+  if (!radioState) {
+    debug("Received null radioState from chrome process.");
+    return;
+  }
+  this.cardState = radioState.cardState;
+  for (let key in radioState.voice) {
+    this.voiceConnectionInfo[key] = radioState.voice[key];
+  }
+  for (let key in radioState.data) {
+    this.dataConnectionInfo[key] = radioState.data[key];
+  }
+}
+RILContentHelper.prototype = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIMobileConnectionProvider,
+                                         Ci.nsIRILContentHelper,
+                                         Ci.nsIObserver]),
+  classID:   RILCONTENTHELPER_CID,
+  classInfo: XPCOMUtils.generateCI({classID: RILCONTENTHELPER_CID,
+                                    classDescription: "RILContentHelper",
+                                    interfaces: [Ci.nsIMobileConnectionProvider,
+                                                 Ci.nsIRILContentHelper]}),
+
+  // nsIRILContentHelper
+
+  cardState:           RIL.GECKO_CARDSTATE_UNAVAILABLE,
+  voiceConnectionInfo: null,
+  dataConnectionInfo:  null,
+
+  getNetworks: function getNetworks(window) {
+    //TODO bug 744344
+    throw Components.Exception("Not implemented", Cr.NS_ERROR_NOT_IMPLEMENTED);
+  },
+
+  // nsIObserver
+
+  observe: function observe(subject, topic, data) {
+    if (topic == "xpcom-shutdown") {
+      for each (let msgname in RIL_IPC_MSG_NAMES) {
+        cpmm.removeMessageListener(msgname, this);
+      }
+      Services.obs.removeObserver(this, "xpcom-shutdown");
+      cpmm = null;
+    }
+  },
+
+  // nsIFrameMessageListener
+
+  receiveMessage: function receiveMessage(msg) {
+    debug("Received message '" + msg.name + "': " + JSON.stringify(msg.json));
+    switch (msg.name) {
+      case "RIL:CardStateChanged":
+        if (this.cardState != msg.json.cardState) {
+          this.cardState = msg.json.cardState;
+          Services.obs.notifyObservers(null, kCardStateChangedTopic, null);
+        }
+        break;
+      case "RIL:VoiceInfoChanged":
+        for (let key in msg.json) {
+          this.voiceConnectionInfo[key] = msg.json[key];
+        }
+        Services.obs.notifyObservers(null, kVoiceChangedTopic, null);
+        break;
+      case "RIL:DataInfoChanged":
+        for (let key in msg.json) {
+          this.dataConnectionInfo[key] = msg.json[key];
+        }
+        Services.obs.notifyObservers(null, kDataChangedTopic, null);
+        break;
+    }
+  },
+
+};
+
+const NSGetFactory = XPCOMUtils.generateNSGetFactory([RILContentHelper]);
+
+let debug;
+if (DEBUG) {
+  debug = function (s) {
+    dump("-*- RILContentHelper: " + s + "\n");
+  };
+} else {
+  debug = function (s) {};
+}
--- a/dom/system/gonk/RadioInterfaceLayer.js
+++ b/dom/system/gonk/RadioInterfaceLayer.js
@@ -69,16 +69,20 @@ XPCOMUtils.defineLazyServiceGetter(this,
 XPCOMUtils.defineLazyServiceGetter(this, "gSmsRequestManager",
                                    "@mozilla.org/sms/smsrequestmanager;1",
                                    "nsISmsRequestManager");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gSmsDatabaseService",
                                    "@mozilla.org/sms/rilsmsdatabaseservice;1",
                                    "nsISmsDatabaseService");
 
+XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
+                                   "@mozilla.org/parentprocessmessagemanager;1",
+                                   "nsIFrameMessageManager");
+
 function convertRILCallState(state) {
   switch (state) {
     case RIL.CALL_STATE_ACTIVE:
       return nsIRadioInterfaceLayer.CALL_STATE_CONNECTED;
     case RIL.CALL_STATE_HOLDING:
       return nsIRadioInterfaceLayer.CALL_STATE_HELD;
     case RIL.CALL_STATE_DIALING:
       return nsIRadioInterfaceLayer.CALL_STATE_DIALING;
@@ -132,131 +136,118 @@ DataCallInfo.protoptype = {
   classInfo:    XPCOMUtils.generateCI({classID: DATACALLINFO_CID,
                                        classDescription: "DataCallInfo",
                                        interfaces: [Ci.nsIDataCallInfo]}),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIDataCallInfo]),
 };
 
 
 function RadioInterfaceLayer() {
+  debug("Starting RIL Worker");
   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.radioState = {
     radioState:     RIL.GECKO_RADIOSTATE_UNAVAILABLE,
     cardState:      RIL.GECKO_CARDSTATE_UNAVAILABLE,
-    connected:      null,
-    roaming:        null,
-    signalStrength: null,
-    bars:           null,
-    operator:       null,
-    type:           null,
     msisdn:         null,
+
+    // These objects implement the nsIDOMMozMobileConnectionInfo interface,
+    // although the actual implementation lives in the content process.
+    voice:          {connected: false,
+                     emergencyCallsOnly: false,
+                     roaming: false,
+                     operator: null,
+                     type: null,
+                     signalStrength: null,
+                     relSignalStrength: null},
+    data:          {connected: false,
+                     emergencyCallsOnly: false,
+                     roaming: false,
+                     operator: null,
+                     type: null,
+                     signalStrength: null,
+                     relSignalStrength: null},
   };
+  ppmm.addMessageListener("RIL:GetRadioState", this);
+  Services.obs.addObserver(this, "xpcom-shutdown", false);
+
   this._sentSmsEnvelopes = {};
 }
 RadioInterfaceLayer.prototype = {
 
   classID:   RADIOINTERFACELAYER_CID,
   classInfo: XPCOMUtils.generateCI({classID: RADIOINTERFACELAYER_CID,
                                     classDescription: "RadioInterfaceLayer",
                                     interfaces: [Ci.nsIWorkerHolder,
                                                  Ci.nsIRadioInterfaceLayer]}),
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIWorkerHolder,
                                          Ci.nsIRadioInterfaceLayer]),
 
+  /**
+   * Process a message from the content process.
+   */
+  receiveMessage: function receiveMessage(msg) {
+    debug("Received '" + msg.name + "' message from content process");
+    switch (msg.name) {
+      case "RIL:GetRadioState":
+        // This message is sync.
+        return this.radioState;
+    }
+  },
+
   onerror: function onerror(event) {
     debug("Got an error: " + event.filename + ":" +
           event.lineno + ": " + event.message + "\n");
     event.preventDefault();
   },
 
   /**
-   * Process the incoming message from the RIL worker:
-   * (1) Update the current state. This way any component that hasn't
-   *     been listening for callbacks can easily catch up by looking at
-   *     this.radioState.
+   * Process the incoming message from the RIL worker. This roughly
+   * works as follows:
+   * (1) Update local state.
    * (2) Update state in related systems such as the audio.
-   * (3) Multiplex the message to telephone callbacks.
+   * (3) Multiplex the message to callbacks / listeners (typically the DOM).
    */
   onmessage: function onmessage(event) {
     let message = event.data;
-    debug("Received message: " + JSON.stringify(message));
+    debug("Received message from worker: " + JSON.stringify(message));
     switch (message.type) {
       case "callStateChange":
         // This one will handle its own notifications.
         this.handleCallStateChange(message.call);
         break;
       case "callDisconnected":
         // This one will handle its own notifications.
         this.handleCallDisconnected(message.call);
         break;
       case "enumerateCalls":
         // This one will handle its own notifications.
         this.handleEnumerateCalls(message.calls);
         break;
       case "voiceregistrationstatechange":
-        this.updateDataConnection(message.voiceRegistrationState);
+        this.updateVoiceConnection(message);
         break;
       case "dataregistrationstatechange":
-        let state = message.dataRegistrationState;
-        this.updateDataConnection(state);
-
-        //TODO for simplicity's sake, for now we only look at
-        // dataRegistrationState for the radio registration state.
-
-        if (!state || state.regState == RIL.NETWORK_CREG_STATE_UNKNOWN) {
-          this.resetRadioState();
-          this.notifyRadioStateChanged();
-          return;
-        }
-
-        this.radioState.connected =
-          (state.regState == RIL.NETWORK_CREG_STATE_REGISTERED_HOME) ||
-          (state.regState == RIL.NETWORK_CREG_STATE_REGISTERED_ROAMING);
-        this.radioState.roaming =
-          this.radioState.connected &&
-          (state.regState == RIL.NETWORK_CREG_STATE_REGISTERED_ROAMING);
-        this.radioState.type = RIL.GECKO_RADIO_TECH[state.radioTech] || null;
-        this.notifyRadioStateChanged();
+        this.updateDataConnection(message);
         break;
       case "signalstrengthchange":
-        //TODO GSM only?
-        let signalStrength = message.signalStrength.gsmSignalStrength;
-        if (signalStrength == 99) {
-          signalStrength = null;
-        }
-        this.radioState.signalStrength = signalStrength;
-        if (message.signalStrength.bars) {
-          this.radioState.bars = message.signalStrength.bars;
-        } else if (signalStrength != null) {
-          //TODO pretty sure that the bars aren't linear, but meh...
-          // Convert signal strength (0...31) to bars (0...4).
-          this.radioState.bars = Math.round(signalStrength / 7.75);
-        } else {
-          this.radioState.bars = null;
-        }
-        this.notifyRadioStateChanged();
+        this.handleSignalStrengthChange(message);
         break;
       case "operatorchange":
-        this.radioState.operator = message.operator.alphaLong;
-        this.notifyRadioStateChanged();
+        this.handleOperatorChange(message);
         break;
       case "radiostatechange":
         this.radioState.radioState = message.radioState;
-        this.notifyRadioStateChanged();
         break;
       case "cardstatechange":
         this.radioState.cardState = message.cardState;
-        if (!message.cardState || message.cardState == "absent") {
-          this.resetRadioState();
-        }
-        this.notifyRadioStateChanged();
+        ppmm.sendAsyncMessage("RIL:CardStateChange", message);
         break;
       case "sms-received":
         this.handleSmsReceived(message);
         return;
       case "sms-sent":
         this.handleSmsSent(message);
         return;
       case "sms-delivered":
@@ -287,16 +278,44 @@ RadioInterfaceLayer.prototype = {
       case "siminfo":
         this.radioState.msisdn = message.msisdn;
         break;
       default:
         throw new Error("Don't know about this message type: " + message.type);
     }
   },
 
+  updateVoiceConnection: function updateVoiceConnection(state) {
+    let voiceInfo = this.radioState.voice;
+    voiceInfo.type = "gsm"; //TODO see bug 726098.
+    if (!state || state.regState == RIL.NETWORK_CREG_STATE_UNKNOWN) {
+      voiceInfo.connected = false;
+      voiceInfo.emergencyCallsOnly = false;
+      voiceInfo.roaming = false;
+      voiceInfo.operator = null;
+      voiceInfo.type = null;
+      voiceInfo.signalStrength = null;
+      voiceInfo.relSignalStrength = null;
+      ppmm.sendAsyncMessage("RIL:VoiceThis.RadioState.VoiceChanged",
+                            voiceInfo);
+      return;
+    }
+    //TODO emergency calls
+    voiceInfo.connected =
+      (state.regState == RIL.NETWORK_CREG_STATE_REGISTERED_HOME) ||
+      (state.regState == RIL.NETWORK_CREG_STATE_REGISTERED_ROAMING);
+    voiceInfo.roaming =
+      voiceInfo.connected &&
+      (state == RIL.NETWORK_CREG_STATE_REGISTERED_ROAMING);    
+    voiceInfo.type =
+      RIL.GECKO_RADIO_TECH[state.radioTech] || null;
+    ppmm.sendAsyncMessage("RIL:VoiceInfoChanged", voiceInfo);
+
+  },
+
   _isDataEnabled: function _isDataEnabled() {
     try {
       return Services.prefs.getBoolPref("ril.data.enabled");
     } catch(ex) {
       return false;
     }
   },
 
@@ -320,16 +339,42 @@ RadioInterfaceLayer.prototype = {
     let haveDataConnection =
       state.radioTech != RIL.NETWORK_CREG_TECH_UNKNOWN;
 
     if (isRegistered && haveDataConnection) {
       debug("Radio is ready for data connection.");
       // RILNetworkInterface will ignore this if it's already connected.
       RILNetworkInterface.connect();
     }
+    //TODO need to keep track of some of the state information, and then
+    // notify the content when state changes (connected, technology
+    // changes, etc.). This should be done in RILNetworkInterface.
+  },
+
+  handleSignalStrengthChange: function handleSignalStrengthChange(message) {
+    // TODO CDMA, EVDO, LTE, etc. (see bug 726098)
+    this.radioState.voice.signalStrength = message.gsmDBM;
+    this.radioState.voice.relSignalStrength = message.gsmRelative;
+    ppmm.sendAsyncMessage("RIL:VoiceInfoChanged", this.radioState.voice);
+
+    this.radioState.data.signalStrength = message.gsmDBM;
+    this.radioState.data.relSignalStrength = message.gsmRelative;
+    ppmm.sendAsyncMessage("RIL:DataInfoChanged", this.radioState.data);
+  },
+
+  handleOperatorChange: function handleOperatorChange(message) {
+    let operator = message.alphaLong;
+    if (operator != this.radioState.voice.operator) {
+      this.radioState.voice.operator = operator;
+      ppmm.sendAsyncMessage("RIL:VoiceInfoChanged", this.radioState.voice);
+    }
+    if (operator != this.radioState.data.operator) {
+      this.radioState.data.operator = operator;
+      ppmm.sendAsyncMessage("RIL:DataInfoChanged", this.radioState.data);
+    }
   },
 
   /**
    * Track the active call and update the audio system as its state changes.
    *
    * XXX Needs some more work to support hold/resume.
    */
   _activeCall: null,
@@ -520,28 +565,24 @@ RadioInterfaceLayer.prototype = {
       datacalls.push(new DataCallInfo(datacall.state,
                                       datacall.cid,
                                       datacall.apn));
     }
     this._deliverDataCallCallback("receiveDataCallList",
                                   [datacalls, datacalls.length]);
   },
 
-  resetRadioState: function resetRadioState() {
-    this.radioState.connected = null;
-    this.radioState.roaming = null;
-    this.radioState.signalStrength = null;
-    this.radioState.bars = null;
-    this.radioState.operator = null;
-    this.radioState.type = null;
-  },
+  // nsIObserver
 
-  notifyRadioStateChanged: function notifyRadioStateChanged() {
-    debug("Radio state changed: " + JSON.stringify(this.radioState));
-    Services.obs.notifyObservers(null, "ril-radiostate-changed", null);
+  observe: function observe(subject, topic, data) {
+    if (topic == "xpcom-shutdown") {
+      ppmm.removeMessageListener("RIL:GetRadioState", this);
+      Services.obs.removeObserver(this, "xpcom-shutdown");
+      ppmm = null;
+    }
   },
 
   // nsIRadioWorker
 
   worker: null,
 
   // nsIRadioInterfaceLayer
 
--- a/dom/system/gonk/RadioInterfaceLayer.manifest
+++ b/dom/system/gonk/RadioInterfaceLayer.manifest
@@ -1,1 +1,7 @@
+# RadioInterfaceLayer.js
 component {2d831c8d-6017-435b-a80c-e5d422810cea} RadioInterfaceLayer.js
+
+# RILContentHelper.js
+component {472816e1-1fd6-4405-996c-806f9ea68174} RILContentHelper.js
+contract @mozilla.org/ril/content-helper;1 {472816e1-1fd6-4405-996c-806f9ea68174}
+category profile-after-change RILContentHelper @mozilla.org/ril/content-helper;1
--- a/dom/system/gonk/nsIRadioInterfaceLayer.idl
+++ b/dom/system/gonk/nsIRadioInterfaceLayer.idl
@@ -33,16 +33,21 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsISupports.idl"
+#include "nsIMobileConnectionProvider.idl"
+
+interface nsIDOMMozMobileConnectionInfo;
+interface nsIDOMDOMRequest;
+interface nsIDOMWindow;
 
 [scriptable, uuid(03eafd60-d138-4f09-81b4-90cd4996b3c7)]
 interface nsIRILTelephonyCallback : nsISupports
 {
   /**
    * Notified when a telephony call changes state.
    *
    * @param callIndex
@@ -111,16 +116,25 @@ interface nsIRILDataCallback : nsISuppor
    *        Array of nsIRILDataCallInfo objects.
    * @param length
    *        Lenght of the aforementioned array.
    */
   void receiveDataCallList([array,size_is(length)] in nsIRILDataCallInfo dataCalls,
                            in unsigned long length);
 };
 
+/**
+ * Helper that runs in the content process and exposes information
+ * to the DOM.
+ */
+[scriptable, uuid(cc9832fd-d3ce-4cab-9abe-48c6046bcebe)]
+interface nsIRILContentHelper : nsIMobileConnectionProvider
+{
+};
+
 [scriptable, uuid(d2025763-fc32-436e-b207-0228ea1ccd12)]
 interface nsIRadioInterfaceLayer : nsISupports
 {
   const unsigned short CALL_STATE_UNKNOWN = 0;
   const unsigned short CALL_STATE_DIALING = 1;
   const unsigned short CALL_STATE_ALERTING = 2;
   const unsigned short CALL_STATE_BUSY = 3;
   const unsigned short CALL_STATE_CONNECTING = 4;
--- a/dom/system/gonk/nsRadioInterfaceLayer.h
+++ b/dom/system/gonk/nsRadioInterfaceLayer.h
@@ -30,12 +30,18 @@
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
-// This must always match the CID given in RadioInterfaceLayer.manifest!
+// These must always match the values given in RadioInterfaceLayer.manifest!
+
 #define NS_RADIOINTERFACELAYER_CID \
     { 0x2d831c8d, 0x6017, 0x435b, \
       { 0xa8, 0x0c, 0xe5, 0xd4, 0x22, 0x81, 0x0c, 0xea } }
+
+#define NS_RILCONTENTHELPER_CID \
+    { 0x472816e1, 0x1fd6, 0x4405, \
+      { 0x99, 0x6c, 0x80, 0x6f, 0x9e, 0xa6, 0x81, 0x74 } }
+#define NS_RILCONTENTHELPER_CONTRACTID "@mozilla.org/ril/content-helper;1"
--- a/dom/system/gonk/ril_worker.js
+++ b/dom/system/gonk/ril_worker.js
@@ -1347,18 +1347,18 @@ let RIL = {
       let networkId = RIL.parseInt(state[9]);
       let roamingIndicator = RIL.parseInt(state[10]);
       let systemIsInPRL = RIL.parseInt(state[11]);
       let defaultRoamingIndicator = RIL.parseInt(state[12]);
       let reasonForDenial = RIL.parseInt(state[13]);
     }
 
     if (stateChanged) {
-      this.sendDOMMessage({type: "voiceregistrationstatechange",
-                           voiceRegistrationState: rs});
+      rs.type = "voiceregistrationstatechange";
+      this.sendDOMMessage(rs);
     }
   },
 
   _processDataRegistrationState: function _processDataRegistrationState(state) {
     let rs = this.dataRegistrationState;
     let stateChanged = false;
 
     let regState = RIL.parseInt(state[0], NETWORK_CREG_STATE_UNKNOWN);
@@ -1369,18 +1369,18 @@ let RIL = {
 
     let radioTech = RIL.parseInt(state[3], NETWORK_CREG_TECH_UNKNOWN);
     if (rs.radioTech != radioTech) {
       rs.radioTech = radioTech;
       stateChanged = true;
     }
 
     if (stateChanged) {
-      this.sendDOMMessage({type: "dataregistrationstatechange",
-                           dataRegistrationState: rs});
+      rs.type = "dataregistrationstatechange";
+      this.sendDOMMessage(rs);
     }
   },
 
   /**
    * Helpers for processing call state.
    */
   _processCalls: function _processCalls(newCalls) {
     // Go through the calls we currently have on file and see if any of them
@@ -1425,26 +1425,23 @@ let RIL = {
 
     // Update our mute status. If there is anything in our currentCalls map then
     // we know it's a voice call and we should leave audio on.
     this.muted = Object.getOwnPropertyNames(this.currentCalls).length == 0;
   },
 
   _handleChangedCallState: function _handleChangedCallState(changedCall) {
     let message = {type: "callStateChange",
-                   call: {callIndex: changedCall.callIndex,
-                          state: changedCall.state,
-                          number: changedCall.number,
-                          name: changedCall.name}};
+                   call: changedCall};
     this.sendDOMMessage(message);
   },
 
   _handleDisconnectedCall: function _handleDisconnectedCall(disconnectedCall) {
     let message = {type: "callDisconnected",
-                   call: {callIndex: disconnectedCall.callIndex}};
+                   call: disconnectedCall};
     this.sendDOMMessage(message);
   },
 
   _processDataCallList: function _processDataCallList(datacalls) {
     for each (let currentDataCall in this.currentDataCalls) {
       let updatedDataCall;
       if (datacalls) {
         updatedDataCall = datacalls[currentDataCall.cid];
@@ -1919,26 +1916,37 @@ RIL[REQUEST_SIGNAL_STRENGTH] = function 
   if (options.rilRequestError) {
     return;
   }
 
   let obj = {};
 
   // GSM
   // Valid values are (0-31, 99) as defined in TS 27.007 8.5.
-  obj.gsmSignalStrength = Buf.readUint32();
-  // The SGS2 seems to compute the number of bars for us and expose those
-  // instead of the actual signal strength.
-  obj.bars = obj.gsmSignalStrength >> 8; //TODO remove this, see bug 729173
-  obj.gsmSignalStrength = obj.gsmSignalStrength & 0xff;
+  let gsmSignalStrength = Buf.readUint32();
+  obj.gsmSignalStrength = gsmSignalStrength & 0xff;
   // GSM bit error rate (0-7, 99) as defined in TS 27.007 8.5.
   obj.gsmBitErrorRate = Buf.readUint32();
 
+  obj.gsmDBM = null;
+  obj.gsmRelative = null;
+  if (obj.gsmSignalStrength >= 0 && obj.gsmSignalStrength <= 31) {
+    obj.gsmDBM = -113 + obj.gsmSignalStrength * 2;
+    obj.gsmRelative = Math.floor(obj.gsmSignalStrength * 100 / 31);
+  }
+
+  // The SGS2 seems to compute the number of bars (0-4) for us and
+  // expose those instead of the actual signal strength. Since the RIL
+  // needs to be "warmed up" first for the quirk detection to work,
+  // we're detecting this ad-hoc and not upfront.
+  if (obj.gsmSignalStrength == 99) {
+    obj.gsmRelative = (gsmSignalStrength >> 8) * 25;
+  }
+
   // CDMA
-  // The CDMA RSSI value.
   obj.cdmaDBM = Buf.readUint32();
   // The CDMA EC/IO.
   obj.cdmaECIO = Buf.readUint32();
   // The EVDO RSSI value.
 
   // EVDO
   obj.evdoDBM = Buf.readUint32();
   // The EVDO EC/IO.
@@ -1959,18 +1967,18 @@ RIL[REQUEST_SIGNAL_STRENGTH] = function 
     // Signal-to-noise ratio for the reference signal.
     // Valid values are -200 (20.0 dB) to +300 (30 dB).
     obj.lteRSSNR = Buf.readUint32();
     // Channel Quality Indicator, valid values are 0 to 15.
     obj.lteCQI = Buf.readUint32();
   }
 
   if (DEBUG) debug("Signal strength " + JSON.stringify(obj));
-  this.sendDOMMessage({type: "signalstrengthchange",
-                       signalStrength: obj});
+  obj.type = "signalstrengthchange";
+  this.sendDOMMessage(obj);
 };
 RIL[REQUEST_VOICE_REGISTRATION_STATE] = function REQUEST_VOICE_REGISTRATION_STATE(length, options) {
   if (options.rilRequestError) {
     return;
   }
 
   let state = Buf.readStringList();
   if (DEBUG) debug("voice registration state: " + state);
@@ -1993,21 +2001,21 @@ RIL[REQUEST_OPERATOR] = function REQUEST
   if (DEBUG) debug("Operator data: " + operator);
   if (operator.length < 3) {
     if (DEBUG) debug("Expected at least 3 strings for operator.");
   }
   if (!this.operator ||
       this.operator.alphaLong  != operator[0] ||
       this.operator.alphaShort != operator[1] ||
       this.operator.numeric    != operator[2]) {
-    this.operator = {alphaLong:  operator[0],
+    this.operator = {type: "operatorchange",
+                     alphaLong:  operator[0],
                      alphaShort: operator[1],
                      numeric:    operator[2]};
-    this.sendDOMMessage({type: "operatorchange",
-                         operator: this.operator});
+    this.sendDOMMessage(this.operator);
   }
 };
 RIL[REQUEST_RADIO_POWER] = null;
 RIL[REQUEST_DTMF] = null;
 RIL[REQUEST_SEND_SMS] = function REQUEST_SEND_SMS(length, options) {
   if (options.rilRequestError) {
     switch (options.rilRequestError) {
       case ERROR_SMS_SEND_FAIL_RETRY: