Bug 783392 - RadioInterfaceLayer broadcasts radio-state information to all content processes. r=marshall_law
authorHsin-Yi Tsai <htsai@mozilla.com>
Tue, 09 Oct 2012 18:07:11 +0800
changeset 110750 9098fb489c6e8556e58bab376b81eff405941e73
parent 110749 96184c897bfcb26e64dd7d0c434bbaa4b231b019
child 110751 11665365c7c07cc383d1d3c5fb962e62df7300a5
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersmarshall_law
bugs783392
milestone19.0a1
Bug 783392 - RadioInterfaceLayer broadcasts radio-state information to all content processes. r=marshall_law
dom/network/interfaces/nsIMobileConnectionProvider.idl
dom/network/src/MobileConnection.cpp
dom/system/gonk/RILContentHelper.js
dom/system/gonk/RadioInterfaceLayer.js
dom/system/gonk/nsIRadioInterfaceLayer.idl
dom/telephony/Telephony.cpp
dom/telephony/Voicemail.cpp
--- a/dom/network/interfaces/nsIMobileConnectionProvider.idl
+++ b/dom/network/interfaces/nsIMobileConnectionProvider.idl
@@ -9,19 +9,29 @@ interface nsIDOMMozMobileConnectionInfo;
 interface nsIDOMMozMobileNetworkInfo;
 interface nsIDOMDOMRequest;
 interface nsIDOMWindow;
 
 /**
  * XPCOM component (in the content process) that provides the mobile
  * network information.
  */
-[scriptable, uuid(bc1a82aa-2a1f-4a27-a5e7-614d06b72e0a)]
+[scriptable, uuid(3db24bfe-abc4-43a3-9183-07e516a33af9)]
 interface nsIMobileConnectionProvider : nsISupports
 {
+  /**
+   * Called when a content process registers receiving unsolicited messages from
+   * RadioInterfaceLayer in the chrome process. Only a content granted
+   * mobileconnection permission is allowed to register. Note that a content
+   * doesn't need to unregister because the chrome process will remove it from
+   * the registration list once the chrome receives a 'child-process-shutdown'
+   * message.
+   */
+  void registerMobileConnectionMsg();
+
   readonly attribute DOMString cardState;
   readonly attribute nsIDOMMozMobileICCInfo iccInfo;
   readonly attribute nsIDOMMozMobileConnectionInfo voiceConnectionInfo;
   readonly attribute nsIDOMMozMobileConnectionInfo dataConnectionInfo;
   readonly attribute DOMString networkSelectionMode;
 
   nsIDOMDOMRequest getNetworks(in nsIDOMWindow window);
   nsIDOMDOMRequest selectNetwork(in nsIDOMWindow window, in nsIDOMMozMobileNetworkInfo network);
--- a/dom/network/src/MobileConnection.cpp
+++ b/dom/network/src/MobileConnection.cpp
@@ -77,16 +77,18 @@ NS_IMPL_EVENT_HANDLER(MobileConnection, 
 MobileConnection::MobileConnection()
 {
   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!");
+  } else {
+    mProvider->RegisterMobileConnectionMsg();
   }
 }
 
 void
 MobileConnection::Init(nsPIDOMWindow* aWindow)
 {
   BindToOwner(aWindow);
 
--- a/dom/system/gonk/RILContentHelper.js
+++ b/dom/system/gonk/RILContentHelper.js
@@ -509,16 +509,31 @@ RILContentHelper.prototype = {
   registerVoicemailCallback: function registerVoicemailCallback(callback) {
     this.registerCallback("_voicemailCallbacks", callback);
   },
 
   unregisterVoicemailCallback: function unregisteVoicemailCallback(callback) {
     this.unregisterCallback("_voicemailCallbacks", callback);
   },
 
+  registerTelephonyMsg: function registerTelephonyMsg() {
+    debug("Registering for telephony-related messages");
+    cpmm.sendAsyncMessage("RIL:RegisterTelephonyMsg");
+  },
+
+  registerMobileConnectionMsg: function registerMobileConnectionMsg() {
+    debug("Registering for mobile connection-related messages");
+    cpmm.sendAsyncMessage("RIL:RegisterMobileConnectionMsg");
+  },
+
+  registerVoicemailMsg: function registerVoicemailMsg() {
+    debug("Registering for voicemail-related messages");
+    cpmm.sendAsyncMessage("RIL:RegisterVoicemailMsg");
+  },
+
   enumerateCalls: function enumerateCalls(callback) {
     debug("Requesting enumeration of calls for callback: " + callback);
     // We need 'requestId' to meet the 'RILContentHelper <--> RadioInterfaceLayer'
     // protocol.
     let requestId = this._getRandomId();
     cpmm.sendAsyncMessage("RIL:EnumerateCalls", {requestId: requestId});
     if (!this._enumerationTelephonyCallbacks) {
       this._enumerationTelephonyCallbacks = [];
--- a/dom/system/gonk/RadioInterfaceLayer.js
+++ b/dom/system/gonk/RadioInterfaceLayer.js
@@ -54,31 +54,37 @@ const RIL_IPC_TELEPHONY_MSG_NAMES = [
   "RIL:StopTone",
   "RIL:Dial",
   "RIL:DialEmergency",
   "RIL:HangUp",
   "RIL:AnswerCall",
   "RIL:RejectCall",
   "RIL:HoldCall",
   "RIL:ResumeCall",
+  "RIL:RegisterTelephonyMsg"
 ];
 
 const RIL_IPC_MOBILECONNECTION_MSG_NAMES = [
   "RIL:GetRilContext",
   "RIL:GetAvailableNetworks",
   "RIL:SelectNetwork",
   "RIL:SelectNetworkAuto",
   "RIL:GetCardLock",
   "RIL:UnlockCardLock",
   "RIL:SetCardLock",
   "RIL:SendMMI",
   "RIL:CancelMMI",
   "RIL:SendStkResponse",
   "RIL:SendStkMenuSelection",
   "RIL:SendStkEventDownload",
+  "RIL:RegisterMobileConnectionMsg"
+];
+
+const RIL_IPC_VOICEMAIL_MSG_NAMES = [
+  "RIL:RegisterVoicemailMsg"
 ];
 
 XPCOMUtils.defineLazyServiceGetter(this, "gSmsService",
                                    "@mozilla.org/sms/smsservice;1",
                                    "nsISmsService");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gSmsRequestManager",
                                    "@mozilla.org/sms/smsrequestmanager;1",
@@ -246,22 +252,30 @@ function RadioInterfaceLayer() {
   lock.get("ril.callwaiting.enabled", this);
 
   // Read the 'time.nitz.automatic-update.enabled' setting to see if
   // we need to adjust the system clock time and time zone by NITZ.
   lock.get(kTimeNitzAutomaticUpdateEnabled, this);
 
   this._messageManagerByRequest = {};
 
+  // Manage message targets in terms of permission. Only the authorized and
+  // registered contents can receive related messages.
+  this._messageManagerByPermission = {};
+
+  ppmm.addMessageListener("child-process-shutdown", this);
   for (let msgname of RIL_IPC_TELEPHONY_MSG_NAMES) {
     ppmm.addMessageListener(msgname, this);
   }
   for (let msgname of RIL_IPC_MOBILECONNECTION_MSG_NAMES) {
     ppmm.addMessageListener(msgname, this);
   }
+  for (let msgname of RIL_IPC_VOICEMAIL_MSG_NAMES) {
+    ppmm.addMessageListener(msgname, this);
+  }
   Services.obs.addObserver(this, "xpcom-shutdown", false);
   Services.obs.addObserver(this, kMozSettingsChangedObserverTopic, false);
   Services.obs.addObserver(this, kSysMsgListenerReadyObserverTopic, false);
 
   this._sentSmsEnvelopes = {};
 
   this.portAddressedSmsApps = {};
   this.portAddressedSmsApps[WAP.WDP_PORT_PUSH] = this.handleSmsWdpPortPush.bind(this);
@@ -279,28 +293,44 @@ RadioInterfaceLayer.prototype = {
                                          Ci.nsIObserver,
                                          Ci.nsISettingsServiceCallback]),
 
   /**
    * Process a message from the content process.
    */
   receiveMessage: function receiveMessage(msg) {
     debug("Received '" + msg.name + "' message from content process");
+    if (msg.name == "child-process-shutdown") {
+      // By the time we receive child-process-shutdown, the child process has
+      // already forgotten its permissions so we need to unregister for both
+      // telephony and mobile connection messages.
+      this.unregisterTelephonyTarget(msg.target);
+      this.unregisterMobileConnectionTarget(msg.target);
+      this.unregisterVoicemailTarget(msg.target);
+      return;
+    }
+
     if (RIL_IPC_TELEPHONY_MSG_NAMES.indexOf(msg.name) != -1) {
       if (!msg.target.assertPermission("telephony")) {
         debug("Telephony message " + msg.name +
               " from a content process with no 'telephony' privileges.");
         return null;
       }
     } else if (RIL_IPC_MOBILECONNECTION_MSG_NAMES.indexOf(msg.name) != -1) {
       if (!msg.target.assertPermission("mobileconnection")) {
         debug("MobileConnection message " + msg.name +
               " from a content process with no 'mobileconnection' privileges.");
         return null;
       }
+    } else if (RIL_IPC_VOICEMAIL_MSG_NAMES.indexOf(msg.name) != -1) {
+      if (!msg.target.assertPermission("voicemail")) {
+        debug("Voicemail message " + msg.name +
+              " from a content process with no 'voicemail' privileges.");
+        return null;
+      }
     } else {
       debug("Ignoring unknown message type: " + msg.name);
       return null;
     }
 
     switch (msg.name) {
       case "RIL:GetRilContext":
         // This message is sync.
@@ -343,27 +373,31 @@ RadioInterfaceLayer.prototype = {
         this.rejectCall(msg.json);
         break;
       case "RIL:HoldCall":
         this.holdCall(msg.json);
         break;
       case "RIL:ResumeCall":
         this.resumeCall(msg.json);
         break;
+      case "RIL:RegisterTelephonyMsg":
+        this.registerTelephonyTarget(msg.target);
+        break;
       case "RIL:GetAvailableNetworks":
         this.saveRequestTarget(msg);
         this.getAvailableNetworks(msg.json.requestId);
         break;
       case "RIL:SelectNetwork":
         this.saveRequestTarget(msg);
         this.selectNetwork(msg.json);
         break;
       case "RIL:SelectNetworkAuto":
         this.saveRequestTarget(msg);
         this.selectNetworkAuto(msg.json.requestId);
+        break;
       case "RIL:GetCardLock":
         this.saveRequestTarget(msg);
         this.getCardLock(msg.json);
         break;
       case "RIL:UnlockCardLock":
         this.saveRequestTarget(msg);
         this.unlockCardLock(msg.json);
         break;
@@ -383,16 +417,22 @@ RadioInterfaceLayer.prototype = {
         this.sendStkResponse(msg.json);
         break;
       case "RIL:SendStkMenuSelection":
         this.sendStkMenuSelection(msg.json);
         break;
       case "RIL:SendStkEventDownload":
         this.sendStkEventDownload(msg.json);
         break;
+      case "RIL:RegisterMobileConnectionMsg":
+        this.registerMobileConnectionTarget(msg.target);
+        break;
+      case "RIL:RegisterVoicemailMsg":
+        this.registerVoicemailTarget(msg.target);
+        break;
     }
   },
 
   onerror: function onerror(event) {
     debug("Got an error: " + event.filename + ":" +
           event.lineno + ": " + event.message + "\n");
     event.preventDefault();
   },
@@ -453,17 +493,17 @@ RadioInterfaceLayer.prototype = {
       case "operatorchange":
         this.handleOperatorChange(message);
         break;
       case "radiostatechange":
         this.handleRadioStateChange(message);
         break;
       case "cardstatechange":
         this.rilContext.cardState = message.cardState;
-        ppmm.broadcastAsyncMessage("RIL:CardStateChanged", message);
+        this._sendMobileConnectionMessage("RIL:CardStateChanged", message);
         break;
       case "setCallWaiting":
         this.handleCallWaitingStatusChange(message);
         break;
       case "sms-received":
         this.handleSmsReceived(message);
         return;
       case "sms-sent":
@@ -500,17 +540,17 @@ RadioInterfaceLayer.prototype = {
         if (callback) {
           delete this._contactsCallbacks[message.requestId];
           callback.receiveContactsList(message.errorMsg,
                                        message.contactType,
                                        message.contacts);
         }
         break;
       case "iccmbdn":
-        ppmm.broadcastAsyncMessage("RIL:VoicemailNumberChanged", message);
+        this._sendVoicemailMessage("RIL:VoicemailNumberChanged", message);
         break;
       case "USSDReceived":
         debug("USSDReceived " + JSON.stringify(message));
         this.handleUSSDReceived(message);
         break;
       case "sendMMI":
       case "sendUSSD":
         this.handleSendMMI(message);
@@ -518,17 +558,17 @@ RadioInterfaceLayer.prototype = {
       case "cancelMMI":
       case "cancelUSSD":
         this.handleCancelMMI(message);
         break;
       case "stkcommand":
         this.handleStkProactiveCommand(message);
         break;
       case "stksessionend":
-        ppmm.broadcastAsyncMessage("RIL:StkSessionEnd", null);
+        this._sendMobileConnectionMessage("RIL:StkSessionEnd", null);
         break;
       case "setPreferredNetworkType":
         this.handleSetPreferredNetworkType(message);
         break;
       case "stkcallsetup":
         // A call has been placed by the STK app. We shall launch the dialer
         // app by sending a system message, in order to further control the
         // call, e.g. hang it up.
@@ -559,16 +599,96 @@ RadioInterfaceLayer.prototype = {
 
     if (!target) {
       return;
     }
 
     target.sendAsyncMessage(requestType, options);
   },
 
+  _messageManagerByPermission: null,
+  registerMessageTarget: function registerMessageTarget(permission, target) {
+    let targets = this._messageManagerByPermission[permission];
+    if (!targets) {
+      targets = this._messageManagerByPermission[permission] = [];
+    }
+
+    if (targets.indexOf(target) != -1) {
+      debug("Already registered this target!");
+      return;
+    }
+
+    targets.push(target);
+    debug("Registered " + permission + " target: " + target);
+  },
+
+  unregisterMessageTarget: function unregisterMessageTarget(permission, target) {
+    let targets = this._messageManagerByPermission[permission];
+    if (!targets) {
+      return;
+    }
+
+    let index = targets.indexOf(target);
+    if (index != -1) {
+      targets.splice(index, 1);
+      debug("Unregistered " + permission + " target: " + target);
+    }
+  },
+
+  registerTelephonyTarget: function registerTelephonyTarget(target) {
+    this.registerMessageTarget("telephony", target);
+  },
+
+  registerMobileConnectionTarget: function registerMobileConnectionTarget(target) {
+    this.registerMessageTarget("mobileconnection", target);
+  },
+
+  registerVoicemailTarget: function registerVoicemailTarget(target) {
+    this.registerMessageTarget("voicemail", target);
+  },
+
+  unregisterTelephonyTarget: function unregisterTelephonyTarget(target) {
+    this.unregisterMessageTarget("telephony", target);
+  },
+
+  unregisterMobileConnectionTarget: function unregisterMobileConnectionTarget(target) {
+    this.unregisterMessageTarget("mobileconnection", target);
+  },
+
+  unregisterVoicemailTarget: function unregisterVoicemailTarget(target) {
+    this.unregisterMessageTarget("voicemail", target);
+  },
+
+  _sendTargetMessage: function _sendTargetMessage(permission, message, options) {
+    let thisTargets = this._messageManagerByPermission[permission];
+    if (!thisTargets) {
+      return;
+    }
+
+    let targets = thisTargets.slice();
+    for each (let target in targets) {
+      if (thisTargets.indexOf(target) == -1) {
+        continue;
+      }
+      target.sendAsyncMessage(message, options);
+    }
+  },
+
+  _sendTelephonyMessage: function sendTelephonyMessage(message, options) {
+    this._sendTargetMessage("telephony", message, options);
+  },
+
+  _sendMobileConnectionMessage: function sendMobileConnectionMessage(message, options) {
+    this._sendTargetMessage("mobileconnection", message, options);
+  },
+
+  _sendVoicemailMessage: function sendVoicemailMessage(message, options) {
+    this._sendTargetMessage("voicemail", message, options);
+  },
+
   updateNetworkInfo: function updateNetworkInfo(message) {
     let voiceMessage = message[RIL.NETWORK_INFO_VOICE_REGISTRATION_STATE];
     let dataMessage = message[RIL.NETWORK_INFO_DATA_REGISTRATION_STATE];
     let operatorMessage = message[RIL.NETWORK_INFO_OPERATOR];
     let selectionMessage = message[RIL.NETWORK_INFO_NETWORK_SELECTION_MODE];
 
     // Batch the *InfoChanged messages together
     if (voiceMessage) {
@@ -592,20 +712,20 @@ RadioInterfaceLayer.prototype = {
         data.network = operatorMessage;
       }
     }
 
     this.checkRoamingBetweenOperators(voice);
     this.checkRoamingBetweenOperators(data);
 
     if (voiceMessage || operatorMessage) {
-      ppmm.broadcastAsyncMessage("RIL:VoiceInfoChanged", voice);
+      this._sendMobileConnectionMessage("RIL:VoiceInfoChanged", voice);
     }
     if (dataMessage || operatorMessage) {
-      ppmm.broadcastAsyncMessage("RIL:DataInfoChanged", data);
+      this._sendMobileConnectionMessage("RIL:DataInfoChanged", data);
     }
 
     if (selectionMessage) {
       this.updateNetworkSelectionMode(selectionMessage);
     }
   },
 
   /**
@@ -669,17 +789,17 @@ RadioInterfaceLayer.prototype = {
     let newCell = newInfo.cell;
     if ((newCell.gsmLocationAreaCode < 0) || (newCell.gsmCellId < 0)) {
       voiceInfo.cell = null;
     } else {
       voiceInfo.cell = newCell;
     }
 
     if (!newInfo.batch) {
-      ppmm.broadcastAsyncMessage("RIL:VoiceInfoChanged", voiceInfo);
+      this._sendMobileConnectionMessage("RIL:VoiceInfoChanged", voiceInfo);
     }
   },
 
   updateDataConnection: function updateDataConnection(newInfo) {
     let dataInfo = this.rilContext.data;
     dataInfo.state = newInfo.state;
     dataInfo.roaming = newInfo.roaming;
     dataInfo.emergencyCallsOnly = newInfo.emergencyCallsOnly;
@@ -699,28 +819,28 @@ RadioInterfaceLayer.prototype = {
     let newCell = newInfo.cell;
     if ((newCell.gsmLocationAreaCode < 0) || (newCell.gsmCellId < 0)) {
       dataInfo.cell = null;
     } else {
       dataInfo.cell = newCell;
     }
 
     if (!newInfo.batch) {
-      ppmm.broadcastAsyncMessage("RIL:DataInfoChanged", dataInfo);
+      this._sendMobileConnectionMessage("RIL:DataInfoChanged", dataInfo);
     }
     this.updateRILNetworkInterface();
   },
 
   /**
    * Handle data errors
    */
   handleDataCallError: function handleDataCallError(message) {
     // Notify data call error only for data APN
     if (message.apn == this.dataCallSettings["apn"]) {
-      ppmm.broadcastAsyncMessage("RIL:DataError", message);
+      this._sendMobileConnectionMessage("RIL:DataError", message);
     }
 
     this._deliverDataCallCallback("dataCallError", [message]);
   },
 
   _preferredNetworkType: null,
   setPreferredNetworkType: function setPreferredNetworkType(value) {
     let networkType = RIL.RIL_PREFERRED_NETWORK_TYPE_TO_GECKO.indexOf(value);
@@ -756,25 +876,25 @@ RadioInterfaceLayer.prototype = {
 
   handleSignalStrengthChange: function handleSignalStrengthChange(message) {
     let voiceInfo = this.rilContext.voice;
     // TODO CDMA, EVDO, LTE, etc. (see bug 726098)
     if (voiceInfo.signalStrength != message.gsmDBM ||
         voiceInfo.relSignalStrength != message.gsmRelative) {
       voiceInfo.signalStrength = message.gsmDBM;
       voiceInfo.relSignalStrength = message.gsmRelative;
-      ppmm.broadcastAsyncMessage("RIL:VoiceInfoChanged", voiceInfo);
+      this._sendMobileConnectionMessage("RIL:VoiceInfoChanged", voiceInfo);
     }
 
     let dataInfo = this.rilContext.data;
     if (dataInfo.signalStrength != message.gsmDBM ||
         dataInfo.relSignalStrength != message.gsmRelative) {
       dataInfo.signalStrength = message.gsmDBM;
       dataInfo.relSignalStrength = message.gsmRelative;
-      ppmm.broadcastAsyncMessage("RIL:DataInfoChanged", dataInfo);
+      this._sendMobileConnectionMessage("RIL:DataInfoChanged", dataInfo);
     }
   },
 
   networkChanged: function networkChanged(srcNetwork, destNetwork) {
     return !destNetwork ||
       destNetwork.longName != srcNetwork.longName ||
       destNetwork.shortName != srcNetwork.shortName ||
       destNetwork.mnc != srcNetwork.mnc ||
@@ -782,22 +902,22 @@ RadioInterfaceLayer.prototype = {
   },
 
   handleOperatorChange: function handleOperatorChange(message) {
     let voice = this.rilContext.voice;
     let data = this.rilContext.data;
 
     if (this.networkChanged(message, voice.network)) {
       voice.network = message;
-      ppmm.broadcastAsyncMessage("RIL:VoiceInfoChanged", voice);
+      this._sendMobileConnectionMessage("RIL:VoiceInfoChanged", voice);
     }
 
     if (this.networkChanged(message, data.network)) {
       data.network = message;
-      ppmm.broadcastAsyncMessage("RIL:DataInfoChanged", data);
+      this._sendMobileConnectionMessage("RIL:DataInfoChanged", data);
     }
   },
 
   handleRadioStateChange: function handleRadioStateChange(message) {
     this._changingRadioPower = false;
 
     let newState = message.radioState;
     if (this.rilContext.radioState == newState) {
@@ -984,30 +1104,30 @@ RadioInterfaceLayer.prototype = {
     if (call.isActive) {
       this._activeCall = call;
     } else if (this._activeCall &&
                this._activeCall.callIndex == call.callIndex) {
       // Previously active call is not active now.
       this._activeCall = null;
     }
     this.updateCallAudioState();
-    ppmm.broadcastAsyncMessage("RIL:CallStateChanged", call);
+    this._sendTelephonyMessage("RIL:CallStateChanged", call);
   },
 
   /**
    * Handle call disconnects by updating our current state and the audio system.
    */
   handleCallDisconnected: function handleCallDisconnected(call) {
     debug("handleCallDisconnected: " + JSON.stringify(call));
     if (call.isActive) {
       this._activeCall = null;
     }
     this.updateCallAudioState();
     call.state = nsIRadioInterfaceLayer.CALL_STATE_DISCONNECTED;
-    ppmm.broadcastAsyncMessage("RIL:CallStateChanged", call);
+    this._sendTelephonyMessage("RIL:CallStateChanged", call);
   },
 
   /**
    * Handle calls delivered in response to a 'enumerateCalls' request.
    */
   handleEnumerateCalls: function handleEnumerateCalls(options) {
     debug("handleEnumerateCalls: " + JSON.stringify(options));
     for (let i in options.calls) {
@@ -1025,17 +1145,17 @@ RadioInterfaceLayer.prototype = {
     this._sendRequestResults("RIL:GetAvailableNetworks", message);
   },
 
   /**
    * Update network selection mode
    */
   updateNetworkSelectionMode: function updateNetworkSelectionMode(message) {
     debug("updateNetworkSelectionMode: " + JSON.stringify(message));
-    ppmm.broadcastAsyncMessage("RIL:NetworkSelectionModeChanged", message);
+    this._sendMobileConnectionMessage("RIL:NetworkSelectionModeChanged", message);
   },
 
   /**
    * Handle "manual" network selection request.
    */
   handleSelectNetwork: function handleSelectNetwork(message) {
     debug("handleSelectNetwork: " + JSON.stringify(message));
     this._sendRequestResults("RIL:SelectNetwork", message);
@@ -1048,17 +1168,17 @@ RadioInterfaceLayer.prototype = {
     debug("handleSelectNetworkAuto: " + JSON.stringify(message));
     this._sendRequestResults("RIL:SelectNetworkAuto", message);
   },
 
   /**
    * Handle call error.
    */
   handleCallError: function handleCallError(message) {
-    ppmm.broadcastAsyncMessage("RIL:CallError", message);   
+    this._sendTelephonyMessage("RIL:CallError", message);
   },
 
   /**
    * Handle WDP port push PDU. Constructor WDP bearer information and deliver
    * to WapPushManager.
    *
    * @param message
    *        A SMS message.
@@ -1109,17 +1229,17 @@ RadioInterfaceLayer.prototype = {
     // For now we don't store indicators persistently. When the mwi.discard
     // flag is false, we'll need to persist the indicator to EFmwis.
     // See TS 23.040 9.2.3.24.2
 
     let mwi = message.mwi;
     if (mwi) {
       mwi.returnNumber = message.sender || null;
       mwi.returnMessage = message.fullBody || null;
-      ppmm.broadcastAsyncMessage("RIL:VoicemailNotification", mwi);
+      this._sendVoicemailMessage("RIL:VoicemailNotification", mwi);
       return;
     }
 
     let id = -1;
     if (message.messageClass != RIL.PDU_DCS_MSG_CLASS_0) {
       id = gSmsDatabaseService.saveReceivedMessage(message.sender || null,
                                                    message.fullBody || null,
                                                    message.timestamp);
@@ -1225,17 +1345,17 @@ RadioInterfaceLayer.prototype = {
    * Handle data call state changes.
    */
   handleDataCallState: function handleDataCallState(datacall) {
     let data = this.rilContext.data;
 
     if (datacall.ifname &&
         datacall.apn == this.dataCallSettings["apn"]) {
       data.connected = (datacall.state == RIL.GECKO_NETWORK_STATE_CONNECTED);
-      ppmm.broadcastAsyncMessage("RIL:DataInfoChanged", data);
+      this._sendMobileConnectionMessage("RIL:DataInfoChanged", data);
     }
 
     this._deliverDataCallCallback("dataCallStateChanged",
                                   [datacall]);
   },
 
   /**
    * Handle data call list.
@@ -1282,26 +1402,26 @@ RadioInterfaceLayer.prototype = {
                          oldIcc.iccid != message.iccid ||
                          oldIcc.mcc != message.mcc || 
                          oldIcc.mnc != message.mnc;
     if (!iccInfoChanged) {
       return;
     }
     // RIL:IccInfoChanged corresponds to a DOM event that gets fired only
     // when the MCC or MNC codes have changed.
-    ppmm.broadcastAsyncMessage("RIL:IccInfoChanged", message);
+    this._sendMobileConnectionMessage("RIL:IccInfoChanged", message);
   },
 
   handleICCCardLockResult: function handleICCCardLockResult(message) {
     this._sendRequestResults("RIL:CardLockResult", message);
   },
 
   handleUSSDReceived: function handleUSSDReceived(ussd) {
     debug("handleUSSDReceived " + JSON.stringify(ussd));
-    ppmm.broadcastAsyncMessage("RIL:USSDReceived", ussd);
+    this._sendMobileConnectionMessage("RIL:UssdReceived", ussd);
   },
 
   handleSendMMI: function handleSendMMI(message) {
     debug("handleSendMMI " + JSON.stringify(message));
     let messageType = message.success ? "RIL:SendMMI:Return:OK" :
                                         "RIL:SendMMI:Return:KO";
     this._sendRequestResults(messageType, message);
   },
@@ -1311,39 +1431,43 @@ RadioInterfaceLayer.prototype = {
     let messageType = message.success ? "RIL:CancelMMI:Return:OK" :
                                         "RIL:CancelMMI:Return:KO";
     this._sendRequestResults(messageType, message);
   },
 
   handleStkProactiveCommand: function handleStkProactiveCommand(message) {
     debug("handleStkProactiveCommand " + JSON.stringify(message));
     gSystemMessenger.broadcastMessage("icc-stkcommand", message);
-    ppmm.broadcastAsyncMessage("RIL:StkCommand", message);
+    this._sendMobileConnectionMessage("RIL:StkCommand", message);
   },
 
   // nsIObserver
 
   observe: function observe(subject, topic, data) {
     switch (topic) {
       case kSysMsgListenerReadyObserverTopic:
         Services.obs.removeObserver(this, kSysMsgListenerReadyObserverTopic);
         this._sysMsgListenerReady = true;
         this._ensureRadioState();
         break;
       case kMozSettingsChangedObserverTopic:
         let setting = JSON.parse(data);
         this.handle(setting.key, setting.value);
         break;
       case "xpcom-shutdown":
+        ppmm.removeMessageListener("child-process-shutdown", this);
         for (let msgname of RIL_IPC_TELEPHONY_MSG_NAMES) {
           ppmm.removeMessageListener(msgname, this);
         }
         for (let msgname of RIL_IPC_MOBILECONNECTION_MSG_NAMES) {
           ppmm.removeMessageListener(msgname, this);
         }
+        for (let msgname of RIL_IPC_VOICEMAIL_MSG_NAMES) {
+          ppmm.removeMessageListener(msgname, this);
+        }
         // Shutdown all RIL network interfaces
         this.dataNetworkInterface.shutdown();
         this.mmsNetworkInterface.shutdown();
         this.suplNetworkInterface.shutdown();
         ppmm = null;
         Services.obs.removeObserver(this, "xpcom-shutdown");
         Services.obs.removeObserver(this, kMozSettingsChangedObserverTopic);
         break;
--- a/dom/system/gonk/nsIRadioInterfaceLayer.idl
+++ b/dom/system/gonk/nsIRadioInterfaceLayer.idl
@@ -134,26 +134,44 @@ interface nsIRILContactCallback : nsISup
                            in DOMString contactType,
                            in jsval contacts);
 };
 
 /**
  * Helper that runs in the content process and exposes information
  * to the DOM.
  */
-[scriptable, uuid(31ea634b-e710-49ce-bb7d-bfcbaa40fb00)]
+[scriptable, uuid(2abe554b-e07c-44b0-9719-02190460188d)]
 interface nsIRILContentHelper : nsIMobileConnectionProvider
 {
   void registerTelephonyCallback(in nsIRILTelephonyCallback callback);
   void unregisterTelephonyCallback(in nsIRILTelephonyCallback callback);
 
   void registerVoicemailCallback(in nsIRILVoicemailCallback callback);
   void unregisterVoicemailCallback(in nsIRILVoicemailCallback callback);
 
   /**
+   * Called when a content process registers receiving unsolicited messages from
+   * RadioInterfaceLayer in the chrome process. Only content granted telephony
+   * permission is allowed to register. Note that a content doesn't need to
+   * unregister because the chrome process will remove it from the registration
+   * list once the chrome receives a 'child-process-shutdown' message.
+   */
+   void registerTelephonyMsg();
+
+  /**
+   * Called when a content process registers receiving unsolicited messages from
+   * RadioInterfaceLayer in the chrome process. Only a content granted voice
+   * mail permission is allowed to register. Note that a content doesn't need to
+   * unregister because the chrome process will remove it from the registration
+   * list once the chrome receives a 'child-process-shutdown' message.
+   */
+   void registerVoicemailMsg();
+
+  /**
    * Will continue calling callback.enumerateCallState until the callback
    * returns false.
    */
   void enumerateCalls(in nsIRILTelephonyCallback callback);
 
   /**
    * Functionality for making and managing phone calls.
    */
--- a/dom/telephony/Telephony.cpp
+++ b/dom/telephony/Telephony.cpp
@@ -89,16 +89,19 @@ Telephony::Create(nsPIDOMWindow* aOwner,
   telephony->mRILTelephonyCallback = new RILTelephonyCallback(telephony);
 
   nsresult rv = aRIL->EnumerateCalls(telephony->mRILTelephonyCallback);
   NS_ENSURE_SUCCESS(rv, nullptr);
 
   rv = aRIL->RegisterTelephonyCallback(telephony->mRILTelephonyCallback);
   NS_ENSURE_SUCCESS(rv, nullptr);
 
+  rv = aRIL->RegisterTelephonyMsg();
+  NS_ENSURE_SUCCESS(rv, nullptr);
+
   return telephony.forget();
 }
 
 already_AddRefed<TelephonyCall>
 Telephony::CreateNewDialingCall(const nsAString& aNumber)
 {
   nsRefPtr<TelephonyCall> call =
     TelephonyCall::Create(this, aNumber,
--- a/dom/telephony/Voicemail.cpp
+++ b/dom/telephony/Voicemail.cpp
@@ -45,16 +45,21 @@ Voicemail::Voicemail(nsPIDOMWindow* aWin
   BindToOwner(aWindow);
 
   mRILVoicemailCallback = new RILVoicemailCallback(this);
 
   nsresult rv = aRIL->RegisterVoicemailCallback(mRILVoicemailCallback);
   if (NS_FAILED(rv)) {
     NS_WARNING("Failed registering voicemail callback with RIL");
   }
+
+  rv = aRIL->RegisterVoicemailMsg();
+  if (NS_FAILED(rv)) {
+    NS_WARNING("Failed registering voicemail messages with RIL");
+  }
 }
 
 Voicemail::~Voicemail()
 {
   if (mRIL && mRILVoicemailCallback) {
     mRIL->UnregisterVoicemailCallback(mRILVoicemailCallback);
   }
 }