Merge b2g-inbound to m-c.
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 21 Nov 2013 15:22:03 -0500
changeset 172456 7225e584d6c754a54e4787e312b0927b315cce99
parent 172419 cdbe5f3adeff24194060f31ee1509b20b9e8b0c6 (current diff)
parent 172455 b36a09f3ae680c083a02773286be4d769156a4d4 (diff)
child 172457 121ce5b3fc114a8f1437722a553b21885a0494ed
child 172481 85abacb35e236f5b7f94e8aa11a3ac7618b18180
child 172482 ee5dbd3936a43256ff7684772546a6c5ef4b3ea5
child 172502 2330857264c4d2068183fa7dfb41eab3cb70c955
child 172512 1971c22c6e7839c312c2a96c5432d5c8bf12561c
push id445
push userffxbld
push dateMon, 10 Mar 2014 22:05:19 +0000
treeherdermozilla-release@dc38b741b04e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone28.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
Merge b2g-inbound to m-c.
CLOBBER
configure.in
--- a/CLOBBER
+++ b/CLOBBER
@@ -13,9 +13,9 @@
 #          |               |
 #          O <-- Clobber   O  <-- Clobber
 #
 # Note: The description below will be part of the error message shown to users.
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
-More Windows WebIDL changes.
+More Windows WebIDL changes. 
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -37,16 +37,22 @@ pref("browser.offline-apps.notify", fals
 pref("browser.cache.offline.enable", true);
 pref("offline-apps.allow_by_default", true);
 
 /* protocol warning prefs */
 pref("network.protocol-handler.warn-external.tel", false);
 pref("network.protocol-handler.warn-external.mailto", false);
 pref("network.protocol-handler.warn-external.vnd.youtube", false);
 
+/* protocol expose prefs */
+// By default, all protocol handlers are exposed. This means that the browser
+// will response to openURL commands for all URL types. It will also try to open
+// link clicks inside the browser before failing over to the system handlers.
+pref("network.protocol-handler.expose.rtsp", false);
+
 /* http prefs */
 pref("network.http.pipelining", true);
 pref("network.http.pipelining.ssl", true);
 pref("network.http.proxy.pipelining", true);
 pref("network.http.pipelining.maxrequests" , 6);
 pref("network.http.keep-alive.timeout", 600);
 pref("network.http.max-connections", 20);
 pref("network.http.max-persistent-connections-per-server", 6);
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,4 +1,4 @@
 {
-    "revision": "89c57a31cebe0d8d94cbce4bcb47788b18f4fe84", 
+    "revision": "94113691294813ec22930d07a5806921056dd856", 
     "repo_path": "/integration/gaia-central"
 }
--- a/configure.in
+++ b/configure.in
@@ -7339,17 +7339,17 @@ if test -n "$MOZ_B2G_BT"; then
 fi
 AC_SUBST(MOZ_B2G_BT)
 AC_SUBST(MOZ_B2G_BT_BLUEZ)
 AC_SUBST(MOZ_B2G_BT_BLUEDROID)
 
 dnl ========================================================
 dnl = Enable NFC Interface for B2G (Gonk usually)
 dnl ========================================================
-MOZ_ARG_ENABLE_BOOL(b2g-nfc,
+MOZ_ARG_ENABLE_BOOL(nfc,
 [  --enable-nfc         Set compile flags necessary for compiling NFC API ],
     MOZ_NFC=1,
     MOZ_NFC= )
 if test -n "$MOZ_NFC"; then
    AC_DEFINE(MOZ_NFC)
 fi
 AC_SUBST(MOZ_NFC)
 
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -765,16 +765,17 @@ GK_ATOM(onpagehide, "onpagehide")
 GK_ATOM(onpageshow, "onpageshow")
 GK_ATOM(onpaint, "onpaint")
 GK_ATOM(onpairedstatuschanged, "onpairedstatuschanged")
 GK_ATOM(onpaste, "onpaste")
 GK_ATOM(onpopuphidden, "onpopuphidden")
 GK_ATOM(onpopuphiding, "onpopuphiding")
 GK_ATOM(onpopupshowing, "onpopupshowing")
 GK_ATOM(onpopupshown, "onpopupshown")
+GK_ATOM(onradiostatechange, "onradiostatechange")
 GK_ATOM(onreadystatechange, "onreadystatechange")
 GK_ATOM(onreceived, "onreceived")
 GK_ATOM(onremoteheld, "onremoteheld")
 GK_ATOM(onremoteresumed, "onremoteresumed")
 GK_ATOM(onretrieving, "onretrieving")
 GK_ATOM(onRequest, "onRequest")
 GK_ATOM(onrequestmediaplaystatus, "onrequestmediaplaystatus")
 GK_ATOM(onreset, "onreset")
--- a/dom/bluetooth/BluetoothRilListener.cpp
+++ b/dom/bluetooth/BluetoothRilListener.cpp
@@ -126,16 +126,22 @@ MobileConnectionListener::NotifyOtaStatu
 }
 
 NS_IMETHODIMP
 MobileConnectionListener::NotifyIccChanged()
 {
   return NS_OK;
 }
 
+NS_IMETHODIMP
+MobileConnectionListener::NotifyRadioStateChanged()
+{
+  return NS_OK;
+}
+
 /**
  *  TelephonyListener Implementation
  *
  *  TODO: Bug 921991 - B2G BT: support multiple sim cards
  */
 class TelephonyListener : public nsITelephonyListener
 {
 public:
--- a/dom/bluetooth/bluedroid/BluetoothHfpManager.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothHfpManager.cpp
@@ -507,23 +507,23 @@ void
 BluetoothHfpManager::ProcessConnectionState(bthf_connection_state_t aState,
                                             bt_bdaddr_t* aBdAddress)
 {
   BT_LOGR("%s: state %d", __FUNCTION__, aState);
 
   mConnectionState = aState;
 
   if (aState == BTHF_CONNECTION_STATE_CONNECTED) {
+    BdAddressTypeToString(aBdAddress, mDeviceAddress);
     BT_HF_DISPATCH_MAIN(MainThreadTaskCmd::NOTIFY_CONN_STATE_CHANGED,
                         NS_LITERAL_STRING(BLUETOOTH_HFP_STATUS_CHANGED_ID));
   } else if (aState == BTHF_CONNECTION_STATE_DISCONNECTED) {
     DisconnectSco();
     BT_HF_DISPATCH_MAIN(MainThreadTaskCmd::NOTIFY_CONN_STATE_CHANGED,
                         NS_LITERAL_STRING(BLUETOOTH_HFP_STATUS_CHANGED_ID));
-    Reset();
   }
 }
 
 void
 BluetoothHfpManager::ProcessAudioState(bthf_audio_state_t aState,
                                        bt_bdaddr_t* aBdAddress)
 {
   BT_LOGR("%s: state %d", __FUNCTION__, aState);
@@ -736,16 +736,18 @@ BluetoothHfpManager::NotifyConnectionSta
 
       // Enumerate current calls
       mListener->EnumerateCalls();
 
       OnConnect(EmptyString());
     } else if (mConnectionState == BTHF_CONNECTION_STATE_DISCONNECTED) {
       mDeviceAddress.AssignLiteral(BLUETOOTH_ADDRESS_NONE);
       OnDisconnect(EmptyString());
+
+      Reset();
     }
   }
 }
 
 void
 BluetoothHfpManager::NotifyDialer(const nsAString& aCommand)
 {
   BluetoothValue v;
--- a/dom/bluetooth/bluedroid/gonk/BluetoothServiceBluedroid.cpp
+++ b/dom/bluetooth/bluedroid/gonk/BluetoothServiceBluedroid.cpp
@@ -70,17 +70,17 @@ static bool sIsBtEnabled = false;
 static nsString sAdapterBdAddress;
 static nsString sAdapterBdName;
 static uint32_t sAdapterDiscoverableTimeout;
 static InfallibleTArray<nsString> sAdapterBondedAddressArray;
 static InfallibleTArray<BluetoothNamedValue> sRemoteDevicesPack;
 static nsTArray<nsRefPtr<BluetoothProfileController> > sControllerArray;
 static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sBondingRunnableArray;
 static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sChangeDiscoveryRunnableArray;
-static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sGetPairedDeviceRunnableArray;
+static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sGetDeviceRunnableArray;
 static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sSetPropertyRunnableArray;
 static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sUnbondingRunnableArray;
 static nsTArray<int> sRequestedDeviceCountArray;
 static StaticAutoPtr<Monitor> sToggleBtMonitor;
 
 /**
  *  Static callback functions
  */
@@ -351,17 +351,17 @@ static void
 RemoteDevicePropertiesChangeCallback(bt_status_t aStatus,
                                      bt_bdaddr_t *aBdAddress,
                                      int aNumProperties,
                                      bt_property_t *aProperties)
 {
   MOZ_ASSERT(!NS_IsMainThread());
 
   if (sRequestedDeviceCountArray.IsEmpty()) {
-    MOZ_ASSERT(sGetPairedDeviceRunnableArray.IsEmpty());
+    MOZ_ASSERT(sGetDeviceRunnableArray.IsEmpty());
     return;
   }
 
   sRequestedDeviceCountArray[0]--;
 
   InfallibleTArray<BluetoothNamedValue> props;
 
   nsString remoteDeviceBdAddress;
@@ -390,31 +390,31 @@ RemoteDevicePropertiesChangeCallback(bt_
     }
   }
 
   // Use address as the index
   sRemoteDevicesPack.AppendElement(
     BluetoothNamedValue(remoteDeviceBdAddress, props));
 
   if (sRequestedDeviceCountArray[0] == 0) {
-    MOZ_ASSERT(!sGetPairedDeviceRunnableArray.IsEmpty());
+    MOZ_ASSERT(!sGetDeviceRunnableArray.IsEmpty());
 
-    if (sGetPairedDeviceRunnableArray.IsEmpty()) {
+    if (sGetDeviceRunnableArray.IsEmpty()) {
       BT_LOGR("No runnable to return");
       return;
     }
 
-    DispatchBluetoothReply(sGetPairedDeviceRunnableArray[0],
+    DispatchBluetoothReply(sGetDeviceRunnableArray[0],
                            sRemoteDevicesPack, EmptyString());
 
     // After firing it, clean up cache
     sRemoteDevicesPack.Clear();
 
     sRequestedDeviceCountArray.RemoveElementAt(0);
-    sGetPairedDeviceRunnableArray.RemoveElementAt(0);
+    sGetDeviceRunnableArray.RemoveElementAt(0);
   }
 }
 
 static void
 DeviceFoundCallback(int aNumProperties, bt_property_t *aProperties)
 {
   MOZ_ASSERT(!NS_IsMainThread());
 
@@ -783,22 +783,64 @@ BluetoothServiceBluedroid::GetDefaultAda
 
   runnable.forget();
 
   return NS_OK;
 }
 
 nsresult
 BluetoothServiceBluedroid::GetConnectedDevicePropertiesInternal(
-  uint16_t aProfileId, BluetoothReplyRunnable* aRunnable)
+  uint16_t aServiceUuid, BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  //FIXME: This will be implemented in later patches
-  DispatchBluetoothReply(aRunnable, BluetoothValue(true), EmptyString());
+  if (!IsReady()) {
+    NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!");
+    DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr);
+    return NS_OK;
+  }
+
+  BluetoothProfileManagerBase* profile =
+    BluetoothUuidHelper::GetBluetoothProfileManager(aServiceUuid);
+  if (!profile) {
+    InfallibleTArray<BluetoothNamedValue> emptyArr;
+    DispatchBluetoothReply(aRunnable, emptyArr,
+                           NS_LITERAL_STRING(ERR_UNKNOWN_PROFILE));
+    return NS_OK;
+  }
+
+  nsTArray<nsString> deviceAddresses;
+  if (profile->IsConnected()) {
+    nsString address;
+    profile->GetAddress(address);
+    deviceAddresses.AppendElement(address);
+  }
+
+  int requestedDeviceCount = deviceAddresses.Length();
+  if (requestedDeviceCount == 0) {
+    InfallibleTArray<BluetoothNamedValue> emptyArr;
+    DispatchBluetoothReply(aRunnable, emptyArr, EmptyString());
+    return NS_OK;
+  }
+
+  for (int i = 0; i < requestedDeviceCount; i++) {
+    // Retrieve all properties of devices
+    bt_bdaddr_t addressType;
+    StringToBdAddressType(deviceAddresses[i], &addressType);
+
+    int ret = sBtInterface->get_remote_device_properties(&addressType);
+    if (ret != BT_STATUS_SUCCESS) {
+      DispatchBluetoothReply(aRunnable, BluetoothValue(true),
+                             NS_LITERAL_STRING("GetConnectedDeviceFailed"));
+      return NS_OK;
+    }
+  }
+
+  sRequestedDeviceCountArray.AppendElement(requestedDeviceCount);
+  sGetDeviceRunnableArray.AppendElement(aRunnable);
 
   return NS_OK;
 }
 
 nsresult
 BluetoothServiceBluedroid::GetPairedDevicePropertiesInternal(
   const nsTArray<nsString>& aDeviceAddress, BluetoothReplyRunnable* aRunnable)
 {
@@ -825,17 +867,17 @@ BluetoothServiceBluedroid::GetPairedDevi
     if (ret != BT_STATUS_SUCCESS) {
       DispatchBluetoothReply(aRunnable, BluetoothValue(true),
                              NS_LITERAL_STRING("GetPairedDeviceFailed"));
       return NS_OK;
     }
   }
 
   sRequestedDeviceCountArray.AppendElement(requestedDeviceCount);
-  sGetPairedDeviceRunnableArray.AppendElement(aRunnable);
+  sGetDeviceRunnableArray.AppendElement(aRunnable);
 
   return NS_OK;
 }
 
 nsresult
 BluetoothServiceBluedroid::StartDiscoveryInternal(
   BluetoothReplyRunnable* aRunnable)
 {
@@ -864,23 +906,25 @@ BluetoothServiceBluedroid::StopDiscovery
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!IsReady()) {
     NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!");
     DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr);
     return NS_OK;
   }
+
   int ret = sBtInterface->cancel_discovery();
   if (ret != BT_STATUS_SUCCESS) {
     ReplyStatusError(aRunnable, ret, NS_LITERAL_STRING("StopDiscovery"));
     return NS_OK;
   }
 
   sChangeDiscoveryRunnableArray.AppendElement(aRunnable);
+
   return NS_OK;
 }
 
 nsresult
 BluetoothServiceBluedroid::GetDevicePropertiesInternal(
   const BluetoothSignal& aSignal)
 {
   return NS_OK;
@@ -1143,22 +1187,24 @@ nsresult
 BluetoothServiceBluedroid::PrepareAdapterInternal()
 {
   return NS_OK;
 }
 
 static void
 NextBluetoothProfileController()
 {
-  sControllerArray[0] = nullptr;
-  sControllerArray.RemoveElementAt(0);
+  MOZ_ASSERT(NS_IsMainThread());
 
-  if (!sControllerArray.IsEmpty()) {
-    sControllerArray[0]->Start();
-  }
+  // First, remove the task at the front which has been already done.
+  NS_ENSURE_FALSE_VOID(sControllerArray.IsEmpty());
+  sControllerArray.RemoveElementAt(0);
+  // Re-check if the task array is empty, if it's not, the next task will begin.
+  NS_ENSURE_FALSE_VOID(sControllerArray.IsEmpty());
+  sControllerArray[0]->Start();
 }
 
 static void
 ConnectDisconnect(bool aConnect, const nsAString& aDeviceAddress,
                   BluetoothReplyRunnable* aRunnable,
                   uint16_t aServiceUuid, uint32_t aCod = 0)
 {
   MOZ_ASSERT(NS_IsMainThread());
--- a/dom/icc/tests/marionette/test_icc_card_state.js
+++ b/dom/icc/tests/marionette/test_icc_card_state.js
@@ -1,34 +1,27 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_TIMEOUT = 30000;
 MARIONETTE_HEAD_JS = "icc_header.js";
 
 function setRadioEnabled(enabled) {
-  SpecialPowers.addPermission("settings-write", true, document);
+  let connection = navigator.mozMobileConnections[0];
+  ok(connection);
 
-  // TODO: Bug 856553 - [B2G] RIL: need an API to enable/disable radio
-  let settings = navigator.mozSettings;
-  let setLock = settings.createLock();
-  let obj = {
-    "ril.radio.disabled": !enabled
-  };
-  let setReq = setLock.set(obj);
+  let request  = connection.setRadioEnabled(enabled);
 
-  setReq.addEventListener("success", function onSetSuccess() {
-    log("set 'ril.radio.disabled' to " + enabled);
-  });
+  request.onsuccess = function onsuccess() {
+    log('setRadioEnabled: ' + enabled);
+  };
 
-  setReq.addEventListener("error", function onSetError() {
-    ok(false, "cannot set 'ril.radio.disabled' to " + enabled);
-  });
-
-  SpecialPowers.removePermission("settings-write", document);
+  request.onerror = function onerror() {
+    ok(false, "setRadioEnabled should be ok");
+  };
 }
 
 /* Basic test */
 taskHelper.push(function basicTest() {
   is(icc.cardState, "ready", "card state is " + icc.cardState);
   taskHelper.runNext();
 });
 
--- a/dom/icc/tests/marionette/test_icc_info.js
+++ b/dom/icc/tests/marionette/test_icc_info.js
@@ -1,34 +1,27 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_TIMEOUT = 30000;
 MARIONETTE_HEAD_JS = "icc_header.js";
 
 function setRadioEnabled(enabled) {
-  SpecialPowers.addPermission("settings-write", true, document);
+  let connection = navigator.mozMobileConnections[0];
+  ok(connection);
 
-  // TODO: Bug 856553 - [B2G] RIL: need an API to enable/disable radio
-  let settings = navigator.mozSettings;
-  let setLock = settings.createLock();
-  let obj = {
-    "ril.radio.disabled": !enabled
-  };
-  let setReq = setLock.set(obj);
+  let request  = connection.setRadioEnabled(enabled);
 
-  setReq.addEventListener("success", function onSetSuccess() {
-    log("set 'ril.radio.disabled' to " + enabled);
-  });
+  request.onsuccess = function onsuccess() {
+    log('setRadioEnabled: ' + enabled);
+  };
 
-  setReq.addEventListener("error", function onSetError() {
-    ok(false, "cannot set 'ril.radio.disabled' to " + enabled);
-  });
-
-  SpecialPowers.removePermission("settings-write", document);
+  request.onerror = function onerror() {
+    ok(false, "setRadioEnabled should be ok");
+  };
 }
 
 function setEmulatorMccMnc(mcc, mnc) {
   let cmd = "operator set 0 Android,Android," + mcc + mnc;
   emulatorHelper.sendCommand(cmd, function (result) {
     let re = new RegExp("" + mcc + mnc + "$");
     ok(result[0].match(re), "MCC/MNC should be changed.");
   });
--- a/dom/messages/SystemMessagePermissionsChecker.jsm
+++ b/dom/messages/SystemMessagePermissionsChecker.jsm
@@ -98,17 +98,18 @@ this.SystemMessagePermissionsTable = {
   "nfc-manager-tech-discovered": {
     "nfc-manager": []
   },
   "nfc-manager-tech-lost": {
     "nfc-manager": []
   },
   "nfc-powerlevel-change": {
     "settings": ["read", "write"]
-  }
+  },
+  "rtsp-open-video": {},
 };
 
 this.SystemMessagePermissionsChecker = {
   /**
    * Return all the needed permission names for the given system message.
    * @param string aSysMsgName
    *        The system messsage name.
    * @returns object
--- a/dom/network/interfaces/nsIDOMMobileConnection.idl
+++ b/dom/network/interfaces/nsIDOMMobileConnection.idl
@@ -6,17 +6,17 @@
 
 interface nsIDOMEventListener;
 interface nsIDOMDOMRequest;
 interface nsIDOMMozMobileConnectionInfo;
 interface nsIDOMMozMobileNetworkInfo;
 interface nsIDOMMozMobileCellInfo;
 interface nsIDOMMozMobileCFInfo;
 
-[scriptable, builtinclass, uuid(052550e3-7466-4941-80d7-405c169652f9)]
+[scriptable, builtinclass, uuid(4c8331f9-45f3-479d-ac3f-acb60fcc0583)]
 interface nsIDOMMozMobileConnection : nsIDOMEventTarget
 {
   const long ICC_SERVICE_CLASS_VOICE = (1 << 0);
   const long ICC_SERVICE_CLASS_DATA = (1 << 1);
   const long ICC_SERVICE_CLASS_FAX = (1 << 2);
   const long ICC_SERVICE_CLASS_SMS = (1 << 3);
   const long ICC_SERVICE_CLASS_DATA_SYNC = (1 << 4);
   const long ICC_SERVICE_CLASS_DATA_ASYNC = (1 << 5);
@@ -78,16 +78,24 @@ interface nsIDOMMozMobileConnection : ns
   /**
    * The selection mode of the voice and data networks.
    *
    * Possible values: null (unknown), 'automatic', 'manual'
    */
   readonly attribute DOMString networkSelectionMode;
 
   /**
+   * The current radio state.
+   *
+   * Possible values: null (unknown), 'enabling', 'enabled', 'disabling',
+   * 'disabled'
+   */
+  readonly attribute DOMString radioState;
+
+  /**
    * Search for available networks.
    *
    * If successful, the request's onsuccess will be called, and the request's
    * result will be an array of nsIDOMMozMobileNetworkInfo.
    *
    * Otherwise, the request's onerror will be called, and the request's error
    * will be either 'RadioNotAvailable', 'RequestNotSupported',
    * or 'GenericFailure'.
@@ -344,16 +352,34 @@ interface nsIDOMMozMobileConnection : ns
    * If successful, the request's onsuccess will be called.
    *
    * Otherwise, the request's onerror will be called, and the request's error
    * will be either 'RequestNotSupported'  or 'GenericFailure'.
    */
   nsIDOMDOMRequest exitEmergencyCbMode();
 
   /**
+   * Set radio enabled/disabled.
+   *
+   * @param enabled
+   *        True to enable the radio.
+   *
+   * If successful, the request's onsuccess will be called.
+   *
+   * Otherwise, the request's onerror will be called, and the request's error
+   * will be either 'InvalidStateError', 'RadioNotAvailable', or
+   * 'GenericFailure'.
+   *
+   * Note: Request is not available when radioState is null, 'enabling', or
+   *       'disabling'. Calling the function in above conditions will receive
+   *       'InvalidStateError' error.
+   */
+  nsIDOMDOMRequest setRadioEnabled(in boolean enabled);
+
+  /**
    * The 'voicechange' event is notified whenever the voice connection object
    * changes.
    */
   [implicit_jscontext] attribute jsval onvoicechange;
 
   /**
    * The 'datachange' event is notified whenever the data connection object
    * changes values.
@@ -390,16 +416,22 @@ interface nsIDOMMozMobileConnection : ns
    */
   [implicit_jscontext] attribute jsval onotastatuschange;
 
   /**
    * The 'oniccchange' event is notified whenever the iccid value
    * changes.
    */
   [implicit_jscontext] attribute jsval oniccchange;
+
+  /**
+   * The 'onradiostatechange' event is notified whenever the radio state
+   * changes.
+   */
+  [implicit_jscontext] attribute jsval onradiostatechange;
 };
 
 [scriptable, uuid(49706beb-a160-40b7-b745-50f62e389a2c)]
 interface nsIDOMMozMobileConnectionInfo : nsISupports
 {
   /**
    * State of the connection.
    *
--- a/dom/network/interfaces/nsIMobileConnectionProvider.idl
+++ b/dom/network/interfaces/nsIMobileConnectionProvider.idl
@@ -5,17 +5,17 @@
 #include "nsISupports.idl"
 
 interface nsIDOMDOMRequest;
 interface nsIDOMMozMobileCFInfo;
 interface nsIDOMMozMobileConnectionInfo;
 interface nsIDOMMozMobileNetworkInfo;
 interface nsIDOMWindow;
 
-[scriptable, uuid(f02c50d5-9d34-4f24-80eb-527a280e31fa)]
+[scriptable, uuid(5013f5cc-24f9-45dc-ba03-f5dc031a3a6b)]
 interface nsIMobileConnectionListener : nsISupports
 {
   void notifyVoiceChanged();
   void notifyDataChanged();
   void notifyUssdReceived(in DOMString message,
                           in boolean sessionEnded);
   void notifyDataError(in DOMString message);
   void notifyCFStateChange(in boolean success,
@@ -23,39 +23,41 @@ interface nsIMobileConnectionListener : 
                            in unsigned short reason,
                            in DOMString number,
                            in unsigned short timeSeconds,
                            in unsigned short serviceClass);
   void notifyEmergencyCbModeChanged(in boolean active,
                                     in unsigned long timeoutMs);
   void notifyOtaStatusChanged(in DOMString status);
   void notifyIccChanged();
+  void notifyRadioStateChanged();
 };
 
 /**
  * XPCOM component (in the content process) that provides the mobile
  * network information.
  */
-[scriptable, uuid(84278a49-0f05-4585-b3f4-c74882ae5719)]
+[scriptable, uuid(9a804dc4-6900-46af-8c38-3d0f424672b5)]
 interface nsIMobileConnectionProvider : nsISupports
 {
   /**
    * Called when a content process registers receiving unsolicited messages from
    * RadioInterfaceLayer in the chrome process. Only a content process that has
    * the 'mobileconnection' permission is allowed to register.
    */
   void registerMobileConnectionMsg(in unsigned long clientId,
                                    in nsIMobileConnectionListener listener);
   void unregisterMobileConnectionMsg(in unsigned long clientId,
                                      in nsIMobileConnectionListener listener);
 
   nsIDOMMozMobileConnectionInfo getVoiceConnectionInfo(in unsigned long clientId);
   nsIDOMMozMobileConnectionInfo getDataConnectionInfo(in unsigned long clientId);
   DOMString getIccId(in unsigned long clientId);
   DOMString getNetworkSelectionMode(in unsigned long clientId);
+  DOMString getRadioState(in unsigned long clientId);
 
   nsIDOMDOMRequest getNetworks(in unsigned long clientId,
                                in nsIDOMWindow window);
   nsIDOMDOMRequest selectNetwork(in unsigned long clientId,
                                  in nsIDOMWindow window,
                                  in nsIDOMMozMobileNetworkInfo network);
   nsIDOMDOMRequest selectNetworkAutomatically(in unsigned long clientId,
                                               in nsIDOMWindow window);
@@ -104,9 +106,11 @@ interface nsIMobileConnectionProvider : 
   nsIDOMDOMRequest setCallingLineIdRestriction(in unsigned long clientId,
                                                in nsIDOMWindow window,
                                                in unsigned short clirMode);
   nsIDOMDOMRequest getCallingLineIdRestriction(in unsigned long clientId,
                                                in nsIDOMWindow window);
 
   nsIDOMDOMRequest exitEmergencyCbMode(in unsigned long clientId,
                                        in nsIDOMWindow window);
+
+  nsIDOMDOMRequest setRadioEnabled(in unsigned long clientId, in nsIDOMWindow window, in bool enabled);
 };
--- a/dom/network/src/MobileConnection.cpp
+++ b/dom/network/src/MobileConnection.cpp
@@ -73,16 +73,17 @@ NS_IMPL_RELEASE_INHERITED(MobileConnecti
 NS_IMPL_EVENT_HANDLER(MobileConnection, voicechange)
 NS_IMPL_EVENT_HANDLER(MobileConnection, datachange)
 NS_IMPL_EVENT_HANDLER(MobileConnection, ussdreceived)
 NS_IMPL_EVENT_HANDLER(MobileConnection, dataerror)
 NS_IMPL_EVENT_HANDLER(MobileConnection, cfstatechange)
 NS_IMPL_EVENT_HANDLER(MobileConnection, emergencycbmodechange)
 NS_IMPL_EVENT_HANDLER(MobileConnection, otastatuschange)
 NS_IMPL_EVENT_HANDLER(MobileConnection, iccchange)
+NS_IMPL_EVENT_HANDLER(MobileConnection, radiostatechange)
 
 MobileConnection::MobileConnection(uint32_t aClientId)
 : mClientId(aClientId)
 {
   mProvider = do_GetService(NS_RILCONTENTHELPER_CONTRACTID);
   mWindow = nullptr;
 
   // Not being able to acquire the provider isn't fatal since we check
@@ -207,16 +208,27 @@ MobileConnection::GetNetworkSelectionMod
 
   if (!mProvider || !CheckPermission("mobileconnection")) {
      return NS_OK;
   }
   return mProvider->GetNetworkSelectionMode(mClientId, aNetworkSelectionMode);
 }
 
 NS_IMETHODIMP
+MobileConnection::GetRadioState(nsAString& aRadioState)
+{
+  aRadioState.SetIsVoid(true);
+
+  if (!mProvider || !CheckPermission("mobileconnection")) {
+     return NS_OK;
+  }
+  return mProvider->GetRadioState(mClientId, aRadioState);
+}
+
+NS_IMETHODIMP
 MobileConnection::GetNetworks(nsIDOMDOMRequest** aRequest)
 {
   *aRequest = nullptr;
 
   if (!CheckPermission("mobileconnection")) {
     return NS_OK;
   }
 
@@ -514,16 +526,33 @@ MobileConnection::ExitEmergencyCbMode(ns
 
   if (!mProvider) {
     return NS_ERROR_FAILURE;
   }
 
   return mProvider->ExitEmergencyCbMode(mClientId, GetOwner(), aRequest);
 }
 
+NS_IMETHODIMP
+MobileConnection::SetRadioEnabled(bool aEnabled,
+                                  nsIDOMDOMRequest** aRequest)
+{
+  *aRequest = nullptr;
+
+  if (!CheckPermission("mobileconnection")) {
+    return NS_OK;
+  }
+
+  if (!mProvider) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return mProvider->SetRadioEnabled(mClientId, GetOwner(), aEnabled, aRequest);
+}
+
 // nsIMobileConnectionListener
 
 NS_IMETHODIMP
 MobileConnection::NotifyVoiceChanged()
 {
   if (!CheckPermission("mobileconnection")) {
     return NS_OK;
   }
@@ -649,8 +678,18 @@ NS_IMETHODIMP
 MobileConnection::NotifyIccChanged()
 {
   if (!CheckPermission("mobileconnection")) {
     return NS_OK;
   }
 
   return DispatchTrustedEvent(NS_LITERAL_STRING("iccchange"));
 }
+
+NS_IMETHODIMP
+MobileConnection::NotifyRadioStateChanged()
+{
+  if (!CheckPermission("mobileconnection")) {
+    return NS_OK;
+  }
+
+  return DispatchTrustedEvent(NS_LITERAL_STRING("radiostatechange"));
+}
\ No newline at end of file
--- a/dom/network/tests/marionette/manifest.ini
+++ b/dom/network/tests/marionette/manifest.ini
@@ -13,8 +13,9 @@ disabled = Bug 808783
 [test_mobile_data_connection.js]
 [test_mobile_data_location.js]
 [test_mobile_data_state.js]
 [test_mobile_mmi.js]
 [test_mobile_roaming_preference.js]
 [test_call_barring_get_option.js]
 [test_call_barring_set_error.js]
 [test_call_barring_change_password.js]
+[test_mobile_set_radio.js]
new file mode 100644
--- /dev/null
+++ b/dom/network/tests/marionette/test_mobile_set_radio.js
@@ -0,0 +1,166 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+
+const DATA_KEY  = "ril.data.enabled";
+const APN_KEY   = "ril.data.apnSettings";
+
+let Promise = SpecialPowers.Cu.import("resource://gre/modules/Promise.jsm").Promise;
+
+SpecialPowers.setBoolPref("dom.mozSettings.enabled", true);
+SpecialPowers.addPermission("mobileconnection", true, document);
+SpecialPowers.addPermission("settings-read", true, document);
+SpecialPowers.addPermission("settings-write", true, document);
+
+let settings = window.navigator.mozSettings;
+let connection = window.navigator.mozMobileConnections[0];
+ok(connection instanceof MozMobileConnection,
+   "connection is instanceof " + connection.constructor);
+
+function setSetting(key, value) {
+  let deferred = Promise.defer();
+
+  let setLock = settings.createLock();
+  let obj = {};
+  obj[key] = value;
+
+  let setReq = setLock.set(obj);
+  setReq.addEventListener("success", function onSetSuccess() {
+    ok(true, "set '" + key + "' to " + obj[key]);
+    deferred.resolve();
+  });
+  setReq.addEventListener("error", function onSetError() {
+    ok(false, "cannot set '" + key + "'");
+    deferred.reject();
+  });
+
+  return deferred.promise;
+}
+
+function setEmulatorAPN() {
+  let apn =
+    [
+      [
+        {"carrier":"T-Mobile US",
+         "apn":"epc.tmobile.com",
+         "mmsc":"http://mms.msg.eng.t-mobile.com/mms/wapenc",
+         "types":["default","supl","mms"]}
+      ]
+    ];
+  return setSetting(APN_KEY, apn);
+}
+
+function enableData() {
+  log("Turn data on.");
+
+  let deferred = Promise.defer();
+
+  connection.addEventListener("datachange", function ondatachange() {
+    if (connection.data.connected === true) {
+      connection.removeEventListener("datachange", ondatachange);
+      log("mobileConnection.data.connected is now '"
+          + connection.data.connected + "'.");
+      deferred.resolve();
+    }
+  });
+
+  setEmulatorAPN()
+    .then(() => setSetting(DATA_KEY, true));
+
+  return deferred.promise;
+}
+
+function receivedPending(received, pending, nextAction) {
+  let index = pending.indexOf(received);
+  if (index != -1) {
+    pending.splice(index, 1);
+  }
+  if (pending.length === 0) {
+    nextAction();
+  }
+}
+
+function waitRadioState(state) {
+  let deferred = Promise.defer();
+
+  waitFor(function() {
+    deferred.resolve();
+  }, function() {
+    return connection.radioState == state;
+  });
+
+  return deferred.promise;
+}
+
+function setRadioEnabled(enabled, transientState, finalState) {
+  log("setRadioEnabled to " + enabled);
+
+  let deferred = Promise.defer();
+  let done = function() {
+    deferred.resolve();
+  };
+
+  let pending = ["onradiostatechange", "onsuccess"];
+
+  let receivedTransient = false;
+  connection.onradiostatechange = function() {
+    let state = connection.radioState;
+    log("Received 'radiostatechange' event, radioState: " + state);
+
+    if (state == transientState) {
+      receivedTransient = true;
+    } else if (state == finalState) {
+      ok(receivedTransient);
+      receivedPending("onradiostatechange", pending, done);
+    }
+  };
+
+  let req = connection.setRadioEnabled(enabled);
+
+  req.onsuccess = function() {
+    log("setRadioEnabled success");
+    receivedPending("onsuccess", pending, done);
+  };
+
+  req.onerror = function() {
+    ok(false, "setRadioEnabled should not fail");
+    deferred.reject();
+  };
+
+  return deferred.promise;
+}
+
+function testSwitchRadio() {
+  log("= testSwitchRadio =");
+  return waitRadioState("enabled")
+    .then(setRadioEnabled.bind(null, false, "disabling", "disabled"))
+    .then(setRadioEnabled.bind(null, true, "enabling", "enabled"));
+}
+
+function testDisableRadioWhenDataConnected() {
+  log("= testDisableRadioWhenDataConnected =");
+  return waitRadioState("enabled")
+    .then(enableData)
+    .then(setRadioEnabled.bind(null, false, "disabling", "disabled"))
+    .then(() => {
+      // Data should be disconnected.
+      is(connection.data.connected, false);
+    })
+    .then(setRadioEnabled.bind(null, true, "enabling", "enabled"));
+}
+
+function cleanUp() {
+  SpecialPowers.removePermission("mobileconnection", document);
+  SpecialPowers.removePermission("settings-write", document);
+  SpecialPowers.removePermission("settings-read", document);
+  SpecialPowers.clearUserPref("dom.mozSettings.enabled");
+  finish();
+}
+
+testSwitchRadio()
+  .then(testDisableRadioWhenDataConnected)
+  .then(null, () => {
+    ok(false, "promise reject somewhere");
+  })
+  .then(cleanUp);
--- a/dom/system/gonk/RILContentHelper.js
+++ b/dom/system/gonk/RILContentHelper.js
@@ -96,16 +96,18 @@ const RIL_IPC_MSG_NAMES = [
   "RIL:IccOpenChannel",
   "RIL:IccCloseChannel",
   "RIL:IccExchangeAPDU",
   "RIL:ReadIccContacts",
   "RIL:UpdateIccContact",
   "RIL:SetRoamingPreference",
   "RIL:GetRoamingPreference",
   "RIL:ExitEmergencyCbMode",
+  "RIL:SetRadioEnabled",
+  "RIL:RadioStateChanged",
   "RIL:SetVoicePrivacyMode",
   "RIL:GetVoicePrivacyMode",
   "RIL:OtaStatusChanged"
 ];
 
 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
                                    "@mozilla.org/childprocessmessagemanager;1",
                                    "nsISyncMessageSender");
@@ -454,16 +456,17 @@ function RILContentHelper() {
 
   this.rilContexts = [];
   this.voicemailInfos = [];
   this.voicemailStatuses = [];
   for (let clientId = 0; clientId < this.numClients; clientId++) {
     this.rilContexts[clientId] = {
       cardState:            RIL.GECKO_CARDSTATE_UNKNOWN,
       networkSelectionMode: RIL.GECKO_NETWORK_SELECTION_UNKNOWN,
+      radioState:           null,
       iccInfo:              null,
       voiceConnectionInfo:  new MobileConnectionInfo(),
       dataConnectionInfo:   new MobileConnectionInfo()
     };
 
     this.voicemailInfos[clientId] = new VoicemailInfo();
   }
 
@@ -606,16 +609,17 @@ RILContentHelper.prototype = {
       let rilContext =
         cpmm.sendSyncMessage("RIL:GetRilContext", {clientId: cId})[0];
       if (!rilContext) {
         if (DEBUG) debug("Received null rilContext from chrome process.");
         continue;
       }
       this.rilContexts[cId].cardState = rilContext.cardState;
       this.rilContexts[cId].networkSelectionMode = rilContext.networkSelectionMode;
+      this.rilContexts[cId].radioState = rilContext.detailedRadioState;
       this.updateIccInfo(cId, rilContext.iccInfo);
       this.updateConnectionInfo(rilContext.voice, this.rilContexts[cId].voiceConnectionInfo);
       this.updateConnectionInfo(rilContext.data, this.rilContexts[cId].dataConnectionInfo);
     }
 
     return this.rilContexts[clientId];
   },
 
@@ -652,16 +656,21 @@ RILContentHelper.prototype = {
     return context && context.iccInfo && context.iccInfo.iccid;
   },
 
   getNetworkSelectionMode: function getNetworkSelectionMode(clientId) {
     let context = this.getRilContext(clientId);
     return context && context.networkSelectionMode;
   },
 
+  getRadioState: function getRadioState(clientId) {
+    let context = this.getRilContext(clientId);
+    return context && context.radioState;
+  },
+
   /**
    * The networks that are currently trying to be selected (or "automatic").
    * This helps ensure that only one network per client is selected at a time.
    */
   _selectingNetworks: null,
 
   getNetworks: function getNetworks(clientId, window) {
     if (window == null) {
@@ -1357,16 +1366,35 @@ RILContentHelper.prototype = {
       data: {
         requestId: requestId,
       }
     });
 
     return request;
   },
 
+  setRadioEnabled: function setRadioEnabled(clientId, window, enabled) {
+    if (window == null) {
+      throw Components.Exception("Can't get window object",
+                                  Cr.NS_ERROR_UNEXPECTED);
+    }
+    let request = Services.DOMRequest.createRequest(window);
+    let requestId = this.getRequestId(request);
+
+    cpmm.sendAsyncMessage("RIL:SetRadioEnabled", {
+      clientId: clientId,
+      data: {
+        requestId: requestId,
+        enabled: enabled,
+      }
+    });
+
+    return request;
+  },
+
   _mobileConnectionListeners: null,
   _cellBroadcastListeners: null,
   _voicemailListeners: null,
   _iccListeners: null,
 
   voicemailInfos: null,
   voicemailStatuses: null,
 
@@ -1762,16 +1790,26 @@ RILContentHelper.prototype = {
         this.handleExitEmergencyCbMode(data);
         break;
       case "RIL:EmergencyCbModeChanged":
         this._deliverEvent(clientId,
                            "_mobileConnectionListeners",
                            "notifyEmergencyCbModeChanged",
                            [data.active, data.timeoutMs]);
         break;
+      case "RIL:SetRadioEnabled":
+        this.handleSimpleRequest(data.requestId, data.errorMsg, null);
+        break;
+      case "RIL:RadioStateChanged":
+        this.rilContexts[clientId].radioState = data;
+        this._deliverEvent(clientId,
+                           "_mobileConnectionListeners",
+                           "notifyRadioStateChanged",
+                           null);
+        break;
       case "RIL:SetVoicePrivacyMode":
         this.handleSimpleRequest(data.requestId, data.errorMsg, null);
         break;
       case "RIL:GetVoicePrivacyMode":
         this.handleSimpleRequest(data.requestId, data.errorMsg,
                                  data.enabled);
         break;
     }
@@ -2089,9 +2127,8 @@ RILContentHelper.prototype = {
 
     return true;
   }
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([RILContentHelper,
                                                      DOMMMIError,
                                                      IccCardLockError]);
-
--- a/dom/system/gonk/RadioInterfaceLayer.js
+++ b/dom/system/gonk/RadioInterfaceLayer.js
@@ -16,16 +16,17 @@
 "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");
 Cu.import("resource://gre/modules/Sntp.jsm");
 Cu.import("resource://gre/modules/systemlibs.js");
+Cu.import("resource://gre/modules/Promise.jsm");
 
 var RIL = {};
 Cu.import("resource://gre/modules/ril_consts.js", RIL);
 
 // set to true in ril_consts.js to see debug messages
 var DEBUG = RIL.DEBUG_RIL;
 
 // Read debug setting from pref
@@ -101,16 +102,17 @@ const RIL_IPC_MOBILECONNECTION_MSG_NAMES
   "RIL:ChangeCallBarringPassword",
   "RIL:SetCallWaitingOptions",
   "RIL:GetCallWaitingOptions",
   "RIL:SetCallingLineIdRestriction",
   "RIL:GetCallingLineIdRestriction",
   "RIL:SetRoamingPreference",
   "RIL:GetRoamingPreference",
   "RIL:ExitEmergencyCbMode",
+  "RIL:SetRadioEnabled",
   "RIL:SetVoicePrivacyMode",
   "RIL:GetVoicePrivacyMode"
 ];
 
 const RIL_IPC_ICCMANAGER_MSG_NAMES = [
   "RIL:SendStkResponse",
   "RIL:SendStkMenuSelection",
   "RIL:SendStkTimerExpiration",
@@ -412,16 +414,21 @@ XPCOMUtils.defineLazyGetter(this, "gMess
 
       let clientId = msg.json.clientId || 0;
       let radioInterface = this.ril.getRadioInterface(clientId);
       if (!radioInterface) {
         if (DEBUG) debug("No such radio interface: " + clientId);
         return null;
       }
 
+      if (msg.name === "RIL:SetRadioEnabled") {
+        // Special handler for SetRadioEnabled.
+        return gRadioEnabledController.receiveMessage(msg);
+      }
+
       return radioInterface.receiveMessage(msg);
     },
 
     /**
      * nsIObserver interface methods.
      */
 
     observe: function observe(subject, topic, data) {
@@ -461,17 +468,141 @@ XPCOMUtils.defineLazyGetter(this, "gMess
       this._sendTargetMessage("icc", message, {
         clientId: clientId,
         data: data
       });
     }
   };
 });
 
-// Initialize shared preference 'ril.numRadioInterfaces' according to system
+XPCOMUtils.defineLazyGetter(this, "gRadioEnabledController", function () {
+  return {
+    ril: null,
+    pendingMessages: [],  // For queueing "RIL:SetRadioEnabled" messages.
+    timer: null,
+    request: null,
+    deactivatingDeferred: {},
+
+    init: function init(ril) {
+      this.ril = ril;
+    },
+
+    receiveMessage: function(msg) {
+      if (DEBUG) debug("setRadioEnabled: receiveMessage: " + JSON.stringify(msg));
+      this.pendingMessages.push(msg);
+      if (this.pendingMessages.length === 1) {
+        this._processNextMessage();
+      }
+    },
+
+    isDeactivatingDataCalls: function() {
+      return this.request !== null;
+    },
+
+    finishDeactivatingDataCalls: function(clientId) {
+      if (DEBUG) debug("setRadioEnabled: finishDeactivatingDataCalls: " + clientId);
+      let deferred = this.deactivatingDeferred[clientId];
+      if (deferred) {
+        deferred.resolve();
+      }
+    },
+
+    _processNextMessage: function() {
+      if (this.pendingMessages.length === 0) {
+        return;
+      }
+
+      let msg = this.pendingMessages.shift();
+      this._handleMessage(msg);
+    },
+
+    _handleMessage: function(msg) {
+      if (DEBUG) debug("setRadioEnabled: handleMessage: " + JSON.stringify(msg));
+      let radioInterface = this.ril.getRadioInterface(msg.json.clientId || 0);
+
+      if (!radioInterface.isValidStateForSetRadioEnabled()) {
+        radioInterface.setRadioEnabledResponse(msg.target, msg.json.data,
+                                               "InvalidStateError");
+        this._processNextMessage();
+        return;
+      }
+
+      if (radioInterface.isDummyForSetRadioEnabled(msg.json.data)) {
+        radioInterface.setRadioEnabledResponse(msg.target, msg.json.data);
+        this._processNextMessage();
+        return;
+      }
+
+      if (msg.json.data.enabled) {
+        radioInterface.receiveMessage(msg);
+        this._processNextMessage();
+      } else {
+        this.request = (function() {
+          radioInterface.receiveMessage(msg);
+          this._processNextMessage();
+        }).bind(this);
+
+        // In some DSDS architecture with only one modem, toggling one radio may
+        // toggle both. Therefore, for safely turning off, we should first
+        // explicitly deactivate all data calls from all clients.
+        this._deactivateDataCalls().then(() => {
+          if (DEBUG) debug("setRadioEnabled: deactivation done");
+          this._executeRequest();
+        });
+
+        this._createTimer();
+      }
+    },
+
+    _deactivateDataCalls: function() {
+      if (DEBUG) debug("setRadioEnabled: deactivating data calls...");
+      this.deactivatingDeferred = {};
+
+      let promise = Promise.resolve();
+      for (let i = 0, N = this.ril.numRadioInterfaces; i < N; ++i) {
+        promise = promise.then(this._deactivateDataCallsForClient(i));
+      }
+
+      return promise;
+    },
+
+    _deactivateDataCallsForClient: function(clientId) {
+      return (function() {
+        let deferred = this.deactivatingDeferred[clientId] = Promise.defer();
+        this.ril.getRadioInterface(clientId).deactivateDataCalls();
+        return deferred.promise;
+      }).bind(this);
+    },
+
+    _createTimer: function() {
+      if (!this.timer) {
+        this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+      }
+      this.timer.initWithCallback(this._executeRequest, RADIO_POWER_OFF_TIMEOUT,
+                                  Ci.nsITimer.TYPE_ONE_SHOT);
+    },
+
+    _cancelTimer: function() {
+      if (this.timer) {
+        this.timer.cancel();
+      }
+    },
+
+    _executeRequest: function() {
+      if (typeof this.request === "function") {
+        if (DEBUG) debug("setRadioEnabled: executeRequest");
+        this._cancelTimer();
+        this.request();
+        this.request = null;
+      }
+    }
+  };
+});
+
+// Initialize shared preference "ril.numRadioInterfaces" according to system
 // property.
 try {
   Services.prefs.setIntPref(kPrefRilNumRadioInterfaces, (function () {
     // When Gonk property "ro.moz.ril.numclients" is not set, return 1; if
     // explicitly set to any number larger-equal than 0, return num; else, return
     // 1 for compatibility.
     try {
       let numString = libcutils.property_get("ro.moz.ril.numclients", "1");
@@ -527,16 +658,17 @@ CdmaIccInfo.prototype = {
 
   // nsIDOMMozCdmaIccInfo
 
   mdn: null
 };
 
 function RadioInterfaceLayer() {
   gMessageManager.init(this);
+  gRadioEnabledController.init(this);
 
   let options = {
     debug: debugPref,
     cellBroadcastDisabled: false,
     clirMode: RIL.CLIR_DEFAULT
   };
 
   try {
@@ -680,17 +812,17 @@ WorkerMessenger.prototype = {
    * Send arbitrary message to worker.
    *
    * @param rilMessageType
    *        A text message type.
    * @param message [optional]
    *        An optional message object to send.
    * @param callback [optional]
    *        An optional callback function which is called when worker replies
-   *        with an message containing a 'rilMessageToken' attribute of the
+   *        with an message containing a "rilMessageToken" attribute of the
    *        same value we passed.  This callback function accepts only one
    *        parameter -- the reply from worker.  It also returns a boolean
    *        value true to keep current token-callback mapping and wait for
    *        another worker reply, or false to remove the mapping.
    */
   send: function send(rilMessageType, message, callback) {
     message = message || {};
 
@@ -715,17 +847,17 @@ WorkerMessenger.prototype = {
   /**
    * Send message to worker and return worker reply to RILContentHelper.
    *
    * @param msg
    *        A message object from ppmm.
    * @param rilMessageType
    *        A text string for worker message type.
    * @param ipcType [optinal]
-   *        A text string for ipc message type. 'msg.name' if omitted.
+   *        A text string for ipc message type. "msg.name" if omitted.
    *
    * @TODO: Bug 815526 - deprecate RILContentHelper.
    */
   sendWithIPCMessage: function sendWithIPCMessage(msg, rilMessageType, ipcType) {
     this.send(rilMessageType, msg.json.data, (function(reply) {
       ipcType = ipcType || msg.name;
       msg.target.sendAsyncMessage(ipcType, {
         clientId: this.radioInterface.clientId,
@@ -753,16 +885,17 @@ function RadioInterface(options) {
   //     via a given APN type.
   this.apnSettings = {
     byType: {},
     byApn: {}
   };
 
   this.rilContext = {
     radioState:     RIL.GECKO_RADIOSTATE_UNAVAILABLE,
+    detailedRadioState: null,
     cardState:      RIL.GECKO_CARDSTATE_UNKNOWN,
     networkSelectionMode: RIL.GECKO_NETWORK_SELECTION_UNKNOWN,
     iccInfo:        null,
     imsi:           null,
 
     // These objects implement the nsIDOMMozMobileConnectionInfo interface,
     // although the actual implementation lives in the content process. So are
     // the child attributes `network` and `cell`, which implement
@@ -787,34 +920,31 @@ function RadioInterface(options) {
 
   this.voicemailInfo = {
     number: null,
     displayName: null
   };
 
   this.operatorInfo = {};
 
-  // Read the 'ril.radio.disabled' setting in order to start with a known
-  // value at boot time.
   let lock = gSettingsService.createLock();
-  lock.get("ril.radio.disabled", this);
 
   // Read preferred network type from the setting DB.
   lock.get("ril.radio.preferredNetworkType", this);
 
   // Read the APN data from the settings DB.
   lock.get("ril.data.roaming_enabled", this);
   lock.get("ril.data.enabled", this);
   lock.get("ril.data.apnSettings", this);
 
-  // Read the 'time.clock.automatic-update.enabled' setting to see if
+  // Read the "time.clock.automatic-update.enabled" setting to see if
   // we need to adjust the system clock time by NITZ or SNTP.
   lock.get(kSettingsClockAutoUpdateEnabled, this);
 
-  // Read the 'time.timezone.automatic-update.enabled' setting to see if
+  // Read the "time.timezone.automatic-update.enabled" setting to see if
   // we need to adjust the system timezone by NITZ.
   lock.get(kSettingsTimezoneAutoUpdateEnabled, this);
 
   // Set "time.clock.automatic-update.available" to false when starting up.
   this.setClockAutoUpdateAvailable(false);
 
   // Set "time.timezone.automatic-update.available" to false when starting up.
   this.setTimezoneAutoUpdateAvailable(false);
@@ -831,21 +961,21 @@ function RadioInterface(options) {
 
   Services.obs.addObserver(this, kNetworkInterfaceStateChangedTopic, false);
   Services.prefs.addObserver(kPrefCellBroadcastDisabled, this, false);
 
   this.portAddressedSmsApps = {};
   this.portAddressedSmsApps[WAP.WDP_PORT_PUSH] = this.handleSmsWdpPortPush.bind(this);
 
   this._sntp = new Sntp(this.setClockBySntp.bind(this),
-                        Services.prefs.getIntPref('network.sntp.maxRetryCount'),
-                        Services.prefs.getIntPref('network.sntp.refreshPeriod'),
-                        Services.prefs.getIntPref('network.sntp.timeout'),
-                        Services.prefs.getCharPref('network.sntp.pools').split(';'),
-                        Services.prefs.getIntPref('network.sntp.port'));
+                        Services.prefs.getIntPref("network.sntp.maxRetryCount"),
+                        Services.prefs.getIntPref("network.sntp.refreshPeriod"),
+                        Services.prefs.getIntPref("network.sntp.timeout"),
+                        Services.prefs.getCharPref("network.sntp.pools").split(";"),
+                        Services.prefs.getIntPref("network.sntp.port"));
 }
 
 RadioInterface.prototype = {
 
   classID:   RADIOINTERFACE_CID,
   classInfo: XPCOMUtils.generateCI({classID: RADIOINTERFACE_CID,
                                     classDescription: "RadioInterface",
                                     interfaces: [Ci.nsIRadioInterface]}),
@@ -858,38 +988,38 @@ RadioInterface.prototype = {
   workerMessenger: null,
 
   debug: function debug(s) {
     dump("-*- RadioInterface[" + this.clientId + "]: " + s + "\n");
   },
 
   /**
    * A utility function to copy objects. The srcInfo may contain
-   * 'rilMessageType', should ignore it.
+   * "rilMessageType", should ignore it.
    */
   updateInfo: function updateInfo(srcInfo, destInfo) {
     for (let key in srcInfo) {
-      if (key === 'rilMessageType') {
+      if (key === "rilMessageType") {
         continue;
       }
       destInfo[key] = srcInfo[key];
     }
   },
 
   /**
    * A utility function to compare objects. The srcInfo may contain
-   * 'rilMessageType', should ignore it.
+   * "rilMessageType", should ignore it.
    */
   isInfoChanged: function isInfoChanged(srcInfo, destInfo) {
     if (!destInfo) {
       return true;
     }
 
     for (let key in srcInfo) {
-      if (key === 'rilMessageType') {
+      if (key === "rilMessageType") {
         continue;
       }
       if (srcInfo[key] !== destInfo[key]) {
         return true;
       }
     }
 
     return false;
@@ -986,16 +1116,19 @@ RadioInterface.prototype = {
         this.setCallingLineIdRestriction(msg.target, msg.json.data);
         break;
       case "RIL:GetCallingLineIdRestriction":
         this.workerMessenger.sendWithIPCMessage(msg, "getCLIR");
         break;
       case "RIL:ExitEmergencyCbMode":
         this.workerMessenger.sendWithIPCMessage(msg, "exitEmergencyCbMode");
         break;
+      case "RIL:SetRadioEnabled":
+        this.setRadioEnabled(msg.target, msg.json.data);
+        break;
       case "RIL:GetVoicemailInfo":
         // This message is sync.
         return this.voicemailInfo;
       case "RIL:SetRoamingPreference":
         this.workerMessenger.sendWithIPCMessage(msg, "setRoamingPreference");
         break;
       case "RIL:GetRoamingPreference":
         this.workerMessenger.sendWithIPCMessage(msg, "queryRoamingPreference");
@@ -1110,20 +1243,16 @@ RadioInterface.prototype = {
         this.handleUSSDReceived(message);
         break;
       case "stkcommand":
         this.handleStkProactiveCommand(message);
         break;
       case "stksessionend":
         gMessageManager.sendIccMessage("RIL:StkSessionEnd", this.clientId, null);
         break;
-      case "setRadioEnabled":
-        let lock = gSettingsService.createLock();
-        lock.set("ril.radio.disabled", !message.on, null, null);
-        break;
       case "exitEmergencyCbMode":
         this.handleExitEmergencyCbMode(message);
         break;
       case "cdma-info-rec-received":
         if (DEBUG) this.debug("cdma-info-rec-received: " + JSON.stringify(message));
         gSystemMessenger.broadcastMessage("cdma-info-rec-received", message);
         break;
       default:
@@ -1476,101 +1605,70 @@ RadioInterface.prototype = {
     }
 
     let status = RIL.CDMA_OTA_PROVISION_STATUS_TO_GECKO[message.status];
 
     gMessageManager.sendMobileConnectionMessage("RIL:OtaStatusChanged",
                                                 this.clientId, status);
   },
 
+  _isRadioChanging: function _isRadioChanging() {
+    let state = this.rilContext.detailedRadioState;
+    return state == RIL.GECKO_DETAILED_RADIOSTATE_ENABLING ||
+      state == RIL.GECKO_DETAILED_RADIOSTATE_DISABLING;
+  },
+
+  _convertRadioState: function _converRadioState(state) {
+    switch (state) {
+      case RIL.GECKO_RADIOSTATE_OFF:
+        return RIL.GECKO_DETAILED_RADIOSTATE_DISABLED;
+      case RIL.GECKO_RADIOSTATE_READY:
+        return RIL.GECKO_DETAILED_RADIOSTATE_ENABLED;
+      default:
+        return RIL.GECKO_DETAILED_RADIOSTATE_UNKNOWN;
+    }
+  },
+
   handleRadioStateChange: function handleRadioStateChange(message) {
-    this._changingRadioPower = false;
-
     let newState = message.radioState;
     if (this.rilContext.radioState == newState) {
       return;
     }
     this.rilContext.radioState = newState;
+    this.handleDetailedRadioStateChanged(this._convertRadioState(newState));
+
     //TODO Should we notify this change as a card state change?
-
-    this._ensureRadioState();
   },
 
-  _ensureRadioState: function _ensureRadioState() {
-    if (DEBUG) {
-      this.debug("Reported radio state is " + this.rilContext.radioState +
-                 ", desired radio enabled state is " + this._radioEnabled);
-    }
-    if (this._radioEnabled == null) {
-      // We haven't read the initial value from the settings DB yet.
-      // Wait for that.
-      return;
-    }
-    if (!this._sysMsgListenerReady) {
-      // The UI's system app isn't ready yet for us to receive any
-      // events (e.g. incoming SMS, etc.). Wait for that.
-      return;
-    }
-    if (this.rilContext.radioState == RIL.GECKO_RADIOSTATE_UNKNOWN) {
-      // We haven't received a radio state notification from the RIL
-      // yet. Wait for that.
-      return;
-    }
-    if (this._changingRadioPower) {
-      // We're changing the radio power currently, ignore any changes.
+  handleDetailedRadioStateChanged: function handleDetailedRadioStateChanged(state) {
+    if (this.rilContext.detailedRadioState == state) {
       return;
     }
-
-    if (this.rilContext.radioState == RIL.GECKO_RADIOSTATE_OFF &&
-        this._radioEnabled) {
-      this._changingRadioPower = true;
-      this.setRadioEnabled(true);
-    }
-    if (this.rilContext.radioState == RIL.GECKO_RADIOSTATE_READY &&
-        !this._radioEnabled) {
-      this._changingRadioPower = true;
-      this.powerOffRadioSafely();
-    }
+    this.rilContext.detailedRadioState = state;
+    gMessageManager.sendMobileConnectionMessage("RIL:RadioStateChanged",
+                                                this.clientId, state);
   },
 
-  _radioOffTimer: null,
-  _cancelRadioOffTimer: function _cancelRadioOffTimer() {
-    if (this._radioOffTimer) {
-      this._radioOffTimer.cancel();
-    }
-  },
-  _fireRadioOffTimer: function _fireRadioOffTimer() {
-    if (DEBUG) this.debug("Radio off timer expired, set radio power off right away.");
-    this.setRadioEnabled(false);
-  },
-
-  /**
-   * Clean up all existing data calls before turning radio off.
-   */
-  powerOffRadioSafely: function powerOffRadioSafely() {
+  deactivateDataCalls: function deactivateDataCalls() {
     let dataDisconnecting = false;
     for each (let apnSetting in this.apnSettings.byApn) {
       for each (let type in apnSetting.types) {
         if (this.getDataCallStateByType(type) ==
             RIL.GECKO_NETWORK_STATE_CONNECTED) {
           this.deactivateDataCallByType(type);
           dataDisconnecting = true;
         }
       }
     }
-    if (dataDisconnecting) {
-      if (this._radioOffTimer == null) {
-        this._radioOffTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
-      }
-      this._radioOffTimer.initWithCallback(this._fireRadioOffTimer.bind(this),
-                                           RADIO_POWER_OFF_TIMEOUT, Ci.nsITimer.TYPE_ONE_SHOT);
-      this._radioOffAfterDataDisconnected = true;
-      return;
+
+    // No data calls exist. It's safe to proceed the pending radio power off
+    // request.
+    if (gRadioEnabledController.isDeactivatingDataCalls() && !dataDisconnecting) {
+      gRadioEnabledController.finishDeactivatingDataCalls(this.clientId);
     }
-    this.setRadioEnabled(false);
   },
 
   /**
    * This function will do the following steps:
    *   1. Clear the cached APN settings in the RIL.
    *   2. Combine APN, user name, and password as the key of |byApn| object to
    *      refer to the corresponding APN setting.
    *   3. Use APN type as the index of |byType| object to refer to the
@@ -1606,18 +1704,18 @@ RadioInterface.prototype = {
       let inputApnSetting = simApnSettings[i];
       if (!this.validateApnSetting(inputApnSetting)) {
         continue;
       }
 
       // Combine APN, user name, and password as the key of |byApn| object to
       // refer to the corresponding APN setting.
       let apnKey = inputApnSetting.apn +
-                   (inputApnSetting.user || '') +
-                   (inputApnSetting.password || '');
+                   (inputApnSetting.user || "") +
+                   (inputApnSetting.password || "");
 
       if (!this.apnSettings.byApn[apnKey]) {
         this.apnSettings.byApn[apnKey] = inputApnSetting;
       } else {
         this.apnSettings.byApn[apnKey].types =
           this.apnSettings.byApn[apnKey].types.concat(inputApnSetting.types);
       }
 
@@ -1724,17 +1822,17 @@ RadioInterface.prototype = {
     if (dataInfo.roaming && !this.dataCallSettings.roamingEnabled) {
       if (DEBUG) this.debug("We're roaming, but data roaming is disabled.");
       return;
     }
     if (wifi_active) {
       if (DEBUG) this.debug("Don't connect data call when Wifi is connected.");
       return;
     }
-    if (this._changingRadioPower) {
+    if (this._isRadioChanging()) {
       // We're changing the radio power currently, ignore any changes.
       return;
     }
 
     if (DEBUG) this.debug("Data call settings: connect data call.");
     this.setupDataCallByType("default");
   },
 
@@ -1931,28 +2029,28 @@ RadioInterface.prototype = {
         result: (success ? RIL.PDU_FCS_OK
                          : RIL.PDU_FCS_MEMORY_CAPACITY_EXCEEDED)
       });
 
       if (!success) {
         // At this point we could send a message to content to notify the user
         // that storing an incoming SMS failed, most likely due to a full disk.
         if (DEBUG) {
-          this.debug("Could not store SMS, error code " + rv);
+          this.debug("Could not store SMS " + message.id + ", error code " + rv);
         }
         return;
       }
 
       this.broadcastSmsSystemMessage(kSmsReceivedObserverTopic, domMessage);
       Services.obs.notifyObservers(domMessage, kSmsReceivedObserverTopic, null);
     }.bind(this);
 
     if (message.messageClass != RIL.GECKO_SMS_MESSAGE_CLASSES[RIL.PDU_DCS_MSG_CLASS_0]) {
-      gMobileMessageDatabaseService.saveReceivedMessage(message,
-                                                        notifyReceived);
+      message.id = gMobileMessageDatabaseService.saveReceivedMessage(message,
+                                                                     notifyReceived);
     } else {
       message.id = -1;
       message.threadId = 0;
       message.delivery = DOM_MOBILE_MESSAGE_DELIVERY_RECEIVED;
       message.deliveryStatus = RIL.GECKO_SMS_DELIVERY_STATUS_SUCCESS;
       message.read = false;
 
       let domMessage =
@@ -2000,34 +2098,32 @@ RadioInterface.prototype = {
     }
 
     this._deliverDataCallCallback("dataCallStateChanged",
                                   [datacall]);
 
     // Process pending radio power off request after all data calls
     // are disconnected.
     if (datacall.state == RIL.GECKO_NETWORK_STATE_UNKNOWN &&
-        this._radioOffAfterDataDisconnected) {
+        gRadioEnabledController.isDeactivatingDataCalls()) {
       let anyDataConnected = false;
       for each (let apnSetting in this.apnSettings.byApn) {
         for each (let type in apnSetting.types) {
           if (this.getDataCallStateByType(type) == RIL.GECKO_NETWORK_STATE_CONNECTED) {
             anyDataConnected = true;
             break;
           }
         }
         if (anyDataConnected) {
           break;
         }
       }
       if (!anyDataConnected) {
-        if (DEBUG) this.debug("All data connections are disconnected, set radio off.");
-        this._radioOffAfterDataDisconnected = false;
-        this._cancelRadioOffTimer();
-        this.setRadioEnabled(false);
+        if (DEBUG) this.debug("All data connections are disconnected.");
+        gRadioEnabledController.finishDeactivatingDataCalls(this.clientId);
       }
     }
   },
 
   /**
    * Handle data call list.
    */
   handleDataCallList: function handleDataCallList(message) {
@@ -2210,19 +2306,17 @@ RadioInterface.prototype = {
     gMessageManager.sendRequestResults("RIL:ExitEmergencyCbMode", message);
   },
 
   // nsIObserver
 
   observe: function observe(subject, topic, data) {
     switch (topic) {
       case kSysMsgListenerReadyObserverTopic:
-        Services.obs.removeObserver(this, kSysMsgListenerReadyObserverTopic);
-        this._sysMsgListenerReady = true;
-        this._ensureRadioState();
+        this.setRadioEnabledInternal({enabled: true}, null);
         break;
       case kMozSettingsChangedObserverTopic:
         let setting = JSON.parse(data);
         this.handleSettingsChange(setting.key, setting.value, setting.message);
         break;
       case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID:
         if (data === kPrefCellBroadcastDisabled) {
           let value = false;
@@ -2267,43 +2361,27 @@ RadioInterface.prototype = {
         }
         break;
       case kScreenStateChangedTopic:
         this.workerMessenger.send("setScreenState", { on: (data === "on") });
         break;
     }
   },
 
-  // Flag to determine whether the UI's system app is ready to receive
-  // events yet.
-  _sysMsgListenerReady: false,
-
-  // Flag to determine the radio state to start with when we boot up. It
-  // corresponds to the 'ril.radio.disabled' setting from the UI.
-  _radioEnabled: null,
-
-  // Flag to ignore any radio power change requests during We're changing
-  // the radio power.
-  _changingRadioPower: false,
-
-  // Flag to determine if we need to set radio off when we are notified a data
-  // call has been disconnected.
-  _radioOffAfterDataDisconnected: false,
-
   // Data calls setting.
   dataCallSettings: null,
 
   apnSettings: null,
 
   // Flag to determine whether to update system clock automatically. It
-  // corresponds to the 'time.clock.automatic-update.enabled' setting.
+  // corresponds to the "time.clock.automatic-update.enabled" setting.
   _clockAutoUpdateEnabled: null,
 
   // Flag to determine whether to update system timezone automatically. It
-  // corresponds to the 'time.clock.automatic-update.enabled' setting.
+  // corresponds to the "time.clock.automatic-update.enabled" setting.
   _timezoneAutoUpdateEnabled: null,
 
   // Remember the last NITZ message so that we can set the time based on
   // the network immediately when users enable network-based time.
   _lastNitzMessage: null,
 
   // Object that handles SNTP.
   _sntp: null,
@@ -2341,21 +2419,16 @@ RadioInterface.prototype = {
     }
 
     this.handle(aName, aResult);
   },
 
   // nsISettingsServiceCallback
   handle: function handle(aName, aResult) {
     switch(aName) {
-      case "ril.radio.disabled":
-        if (DEBUG) this.debug("'ril.radio.disabled' is now " + aResult);
-        this._radioEnabled = !aResult;
-        this._ensureRadioState();
-        break;
       case "ril.radio.preferredNetworkType":
         if (DEBUG) this.debug("'ril.radio.preferredNetworkType' is now " + aResult);
         this.setPreferredNetworkType(aResult);
         break;
       case "ril.data.enabled":
         if (DEBUG) this.debug("'ril.data.enabled' is now " + aResult);
         let enabled;
         if (Array.isArray(aResult)) {
@@ -2417,37 +2490,28 @@ RadioInterface.prototype = {
         this.setCellBroadcastSearchList(aResult);
         break;
     }
   },
 
   handleError: function handleError(aErrorMessage) {
     if (DEBUG) this.debug("There was an error while reading RIL settings.");
 
-    // Default radio to on.
-    this._radioEnabled = true;
-    this._ensureRadioState();
-
     // Clean data call setting.
     this.dataCallSettings.oldEnabled = false;
     this.dataCallSettings.enabled = false;
     this.dataCallSettings.roamingEnabled = false;
     this.apnSettings = {
       byType: {},
       byApn: {},
     };
   },
 
   // nsIRadioInterface
 
-  setRadioEnabled: function setRadioEnabled(value) {
-    if (DEBUG) this.debug("Setting radio power to " + value);
-    this.workerMessenger.send("setRadioPower", { on: value });
-  },
-
   rilContext: null,
 
   // Handle phone functions of nsIRILContentHelper
 
   _sendCfStateChanged: function _sendCfStateChanged(message) {
     gMessageManager.sendMobileConnectionMessage("RIL:CfStateChanged",
                                                 this.clientId, message);
   },
@@ -2505,16 +2569,72 @@ RadioInterface.prototype = {
       target.sendAsyncMessage("RIL:SetCallingLineIdRestriction", {
         clientId: this.clientId,
         data: response
       });
       return false;
     }).bind(this));
   },
 
+  isValidStateForSetRadioEnabled: function() {
+    let state = this.rilContext.radioState;
+
+    return !this._isRadioChanging() &&
+        (state == RIL.GECKO_RADIOSTATE_READY ||
+         state == RIL.GECKO_RADIOSTATE_OFF);
+  },
+
+  isDummyForSetRadioEnabled: function(message) {
+    let state = this.rilContext.radioState;
+
+    return (state == RIL.GECKO_RADIOSTATE_READY && message.enabled) ||
+        (state == RIL.GECKO_RADIOSTATE_OFF && !message.enabled);
+  },
+
+  setRadioEnabledResponse: function(target, message, errorMsg) {
+    if (errorMsg) {
+      message.errorMsg = errorMsg;
+    }
+
+    target.sendAsyncMessage("RIL:SetRadioEnabled", {
+      clientId: this.clientId,
+      data: message
+    });
+  },
+
+  setRadioEnabled: function setRadioEnabled(target, message) {
+    if (DEBUG) {
+      this.debug("setRadioEnabled: " + JSON.stringify(message));
+    }
+
+    if (!this.isValidStateForSetRadioEnabled()) {
+      this.setRadioEnabledResponse(target, message, "InvalidStateError");
+      return;
+    }
+
+    if (this.isDummyForSetRadioEnabled(message)) {
+      this.setRadioEnabledResponse(target, message);
+      return;
+    }
+
+    let callback = (function(response) {
+      this.setRadioEnabledResponse(target, response);
+      return false;
+    }).bind(this);
+
+    this.setRadioEnabledInternal(message, callback);
+  },
+
+  setRadioEnabledInternal: function setRadioEnabledInternal(message, callback) {
+    let state = message.enabled ? RIL.GECKO_DETAILED_RADIOSTATE_ENABLING
+                                : RIL.GECKO_DETAILED_RADIOSTATE_DISABLING;
+    this.handleDetailedRadioStateChanged(state);
+    this.workerMessenger.send("setRadioEnabled", message, callback);
+  },
+
   /**
    * List of tuples of national language identifier pairs.
    *
    * TODO: Support static/runtime settings, see bug 733331.
    */
   enabledGsmTableTuples: [
     [RIL.PDU_NL_IDENTIFIER_DEFAULT, RIL.PDU_NL_IDENTIFIER_DEFAULT],
   ],
@@ -2581,17 +2701,17 @@ RadioInterface.prototype = {
       septet = langShiftTable.indexOf(c);
       if (septet < 0) {
         if (!strict7BitEncoding) {
           return -1;
         }
 
         // Bug 816082, when strict7BitEncoding is enabled, we should replace
         // characters that can't be encoded with GSM 7-Bit alphabets with '*'.
-        c = '*';
+        c = "*";
         if (langTable.indexOf(c) >= 0) {
           length++;
         } else if (langShiftTable.indexOf(c) >= 0) {
           length += 2;
         } else {
           // We can't even encode a '*' character with current configuration.
           return -1;
         }
@@ -2803,17 +2923,17 @@ RadioInterface.prototype = {
         inc = 2;
         if (septet < 0) {
           if (!strict7BitEncoding) {
             throw new Error("Given text cannot be encoded with GSM 7-bit Alphabet!");
           }
 
           // Bug 816082, when strict7BitEncoding is enabled, we should replace
           // characters that can't be encoded with GSM 7-Bit alphabets with '*'.
-          c = '*';
+          c = "*";
           if (langTable.indexOf(c) >= 0) {
             inc = 1;
           }
         }
       }
 
       if ((len + inc) > segmentSeptets) {
         ret.push({
@@ -2973,17 +3093,18 @@ RadioInterface.prototype = {
 
       // If the radio is disabled or the SIM card is not ready, just directly
       // return with the corresponding error code.
       let errorCode;
       if (!PhoneNumberUtils.isPlainPhoneNumber(options.number)) {
         if (DEBUG) this.debug("Error! Address is invalid when sending SMS: " +
                               options.number);
         errorCode = Ci.nsIMobileMessageCallback.INVALID_ADDRESS_ERROR;
-      } else if (!this._radioEnabled) {
+      } else if (this.rilContext.detailedRadioState ==
+                 RIL.GECKO_DETAILED_RADIOSTATE_DISABLED) {
         if (DEBUG) this.debug("Error! Radio is disabled when sending SMS.");
         errorCode = Ci.nsIMobileMessageCallback.RADIO_DISABLED_ERROR;
       } else if (this.rilContext.cardState != "ready") {
         if (DEBUG) this.debug("Error! SIM card is not ready when sending SMS.");
         errorCode = Ci.nsIMobileMessageCallback.NO_SIM_CARD_ERROR;
       }
       if (errorCode) {
         if (silent) {
@@ -3058,17 +3179,17 @@ RadioInterface.prototype = {
                                            (function notifyResult(rv, domMessage) {
             // TODO bug 832140 handle !Components.isSuccessCode(rv)
 
             let topic = (response.deliveryStatus ==
                          RIL.GECKO_SMS_DELIVERY_STATUS_SUCCESS)
                         ? kSmsDeliverySuccessObserverTopic
                         : kSmsDeliveryErrorObserverTopic;
 
-            // Broadcasting a 'sms-delivery-success' system message to open apps.
+            // Broadcasting a "sms-delivery-success" system message to open apps.
             if (topic == kSmsDeliverySuccessObserverTopic) {
               this.broadcastSmsSystemMessage(topic, domMessage);
             }
 
             // Notifying observers the delivery status is updated.
             Services.obs.notifyObservers(domMessage, topic, null);
           }).bind(this));
 
@@ -3147,18 +3268,18 @@ RadioInterface.prototype = {
                                                "normal", // message class
                                                sendingMessage.timestamp,
                                                0,
                                                false);
       notifyResult(Cr.NS_OK, domMessage);
       return;
     }
 
-    gMobileMessageDatabaseService.saveSendingMessage(sendingMessage,
-                                                     notifyResult);
+    let id = gMobileMessageDatabaseService.saveSendingMessage(
+      sendingMessage, notifyResult);
   },
 
   registerDataCallCallback: function registerDataCallCallback(callback) {
     if (this._datacall_callbacks) {
       if (this._datacall_callbacks.indexOf(callback) != -1) {
         throw new Error("Already registered this callback!");
       }
     } else {
@@ -3388,21 +3509,21 @@ RILNetworkInterface.prototype = {
 
   broadcast: null,
 
   dns1: null,
 
   dns2: null,
 
   get httpProxyHost() {
-    return this.apnSetting.proxy || '';
+    return this.apnSetting.proxy || "";
   },
 
   get httpProxyPort() {
-    return this.apnSetting.port || '';
+    return this.apnSetting.port || "";
   },
 
   /**
    * nsIRilNetworkInterface Implementation
    */
 
   get serviceId() {
     return this.radioInterface.clientId;
@@ -3520,17 +3641,17 @@ RILNetworkInterface.prototype = {
     // In current design, we don't update status of secondary APN if it shares
     // same APN name with the default APN.  In this condition, this.cid will
     // not be set and we don't want to update its status.
     if (this.cid == null) {
       return;
     }
 
     if (this.state == datacall.state) {
-      if (datacall.state != GECKO_NETWORK_STATE_CONNECTED) {
+      if (datacall.state != RIL.GECKO_NETWORK_STATE_CONNECTED) {
         return;
       }
       // State remains connected, check for minor changes.
       let changed = false;
       if (this.gateway != datacall.gw) {
         this.gateway = datacall.gw;
         changed = true;
       }
--- a/dom/system/gonk/ril_consts.js
+++ b/dom/system/gonk/ril_consts.js
@@ -2374,16 +2374,22 @@ this.CALL_FAIL_IMSI_UNKNOWN_IN_VLR = 242
 this.CALL_FAIL_IMEI_NOT_ACCEPTED = 243;
 this.CALL_FAIL_ERROR_UNSPECIFIED = 0xffff;
 
 // Other Gecko-specific constants
 this.GECKO_RADIOSTATE_UNAVAILABLE   = null;
 this.GECKO_RADIOSTATE_OFF           = "off";
 this.GECKO_RADIOSTATE_READY         = "ready";
 
+this.GECKO_DETAILED_RADIOSTATE_UNKNOWN    = null;
+this.GECKO_DETAILED_RADIOSTATE_ENABLING   = "enabling";
+this.GECKO_DETAILED_RADIOSTATE_ENABLED    = "enabled";
+this.GECKO_DETAILED_RADIOSTATE_DISABLING  = "disabling";
+this.GECKO_DETAILED_RADIOSTATE_DISABLED   = "disabled";
+
 this.GECKO_CARDSTATE_UNDETECTED                    = null;
 this.GECKO_CARDSTATE_ILLEGAL                       = "illegal";
 this.GECKO_CARDSTATE_UNKNOWN                       = "unknown";
 this.GECKO_CARDSTATE_PIN_REQUIRED                  = "pinRequired";
 this.GECKO_CARDSTATE_PUK_REQUIRED                  = "pukRequired";
 this.GECKO_CARDSTATE_PERSONALIZATION_IN_PROGRESS   = "personalizationInProgress";
 this.GECKO_CARDSTATE_PERSONALIZATION_READY         = "personalizationReady";
 this.GECKO_CARDSTATE_NETWORK_LOCKED                = "networkLocked";
--- a/dom/system/gonk/ril_worker.js
+++ b/dom/system/gonk/ril_worker.js
@@ -939,25 +939,25 @@ let RIL = {
         this.appType, options.contactType, contact, options.pin2, onsuccess, onerror);
     } else {
       ICCContactHelper.addICCContact(
         this.appType, options.contactType, contact, options.pin2, onsuccess, onerror);
     }
   },
 
   /**
-   * Request the phone's radio power to be switched on or off.
-   *
-   * @param on
-   *        Boolean indicating the desired power state.
-   */
-  setRadioPower: function setRadioPower(options) {
+   * Request the phone's radio to be enabled or disabled.
+   *
+   * @param enabled
+   *        Boolean indicating the desired state.
+   */
+  setRadioEnabled: function setRadioEnabled(options) {
     Buf.newParcel(REQUEST_RADIO_POWER, options);
     Buf.writeInt32(1);
-    Buf.writeInt32(options.on ? 1 : 0);
+    Buf.writeInt32(options.enabled ? 1 : 0);
     Buf.sendParcel();
   },
 
   /**
    * Query call waiting status via MMI.
    */
   _handleQueryMMICallWaiting: function _handleQueryMMICallWaiting(options) {
     function callback(options) {
@@ -1408,19 +1408,17 @@ let RIL = {
     if (this.radioState == GECKO_RADIOSTATE_OFF) {
       if (DEBUG) debug("Automatically enable radio for an emergency call.");
 
       if (!this.cachedDialRequest) {
         this.cachedDialRequest = {};
       }
       this.cachedDialRequest.onerror = onerror;
       this.cachedDialRequest.callback = this.sendDialRequest.bind(this, options);
-
-      // Change radio setting value in settings DB to enable radio.
-      this.sendChromeMessage({rilMessageType: "setRadioEnabled", on: true});
+      this.setRadioEnabled({enabled: true});
       return;
     }
 
     this.sendDialRequest(options);
   },
 
   sendDialRequest: function sendDialRequest(options) {
     Buf.newParcel(options.request);
@@ -5179,28 +5177,35 @@ RIL[REQUEST_OPERATOR] = function REQUEST
     return;
   }
 
   let operatorData = Buf.readStringList();
   if (DEBUG) debug("Operator: " + operatorData);
   this._processOperator(operatorData);
 };
 RIL[REQUEST_RADIO_POWER] = function REQUEST_RADIO_POWER(length, options) {
-  if (options.rilRequestError) {
-    if (this.cachedDialRequest && options.on) {
-      // Turning on radio fails. Notify the error of making an emergency call.
-      this.cachedDialRequest.onerror(GECKO_ERROR_RADIO_NOT_AVAILABLE);
-      this.cachedDialRequest = null;
-    }
+  if (options.rilMessageType == null) {
+    // The request was made by ril_worker itself.
+    if (options.rilRequestError) {
+      if (this.cachedDialRequest && options.enabled) {
+        // Turning on radio fails. Notify the error of making an emergency call.
+        this.cachedDialRequest.onerror(GECKO_ERROR_RADIO_NOT_AVAILABLE);
+        this.cachedDialRequest = null;
+      }
+      return;
+    }
+
+    if (this._isInitialRadioState) {
+      this._isInitialRadioState = false;
+    }
+
     return;
   }
 
-  if (this._isInitialRadioState) {
-    this._isInitialRadioState = false;
-  }
+  this.sendChromeMessage(options);
 };
 RIL[REQUEST_DTMF] = null;
 RIL[REQUEST_SEND_SMS] = function REQUEST_SEND_SMS(length, options) {
   this._processSmsSendResult(length, options);
 };
 RIL[REQUEST_SEND_SMS_EXPECT_MORE] = null;
 
 RIL.readSetupDataCall_v5 = function readSetupDataCall_v5(options) {
@@ -6015,17 +6020,17 @@ RIL[REQUEST_GET_UNLOCK_RETRY_COUNT] = fu
 };
 RIL[UNSOLICITED_RESPONSE_RADIO_STATE_CHANGED] = function UNSOLICITED_RESPONSE_RADIO_STATE_CHANGED() {
   let radioState = Buf.readInt32();
 
   // Ensure radio state at boot time.
   if (this._isInitialRadioState) {
     // Even radioState is RADIO_STATE_OFF, we still have to maually turn radio off,
     // otherwise REQUEST_GET_SIM_STATUS will still report CARD_STATE_PRESENT.
-    this.setRadioPower({on: false});
+    this.setRadioEnabled({enabled: false});
   }
 
   let newState;
   if (radioState == RADIO_STATE_UNAVAILABLE) {
     newState = GECKO_RADIOSTATE_UNAVAILABLE;
   } else if (radioState == RADIO_STATE_OFF) {
     newState = GECKO_RADIOSTATE_OFF;
   } else {
--- a/dom/telephony/test/marionette/test_outgoing_emergency_in_airplane_mode.js
+++ b/dom/telephony/test/marionette/test_outgoing_emergency_in_airplane_mode.js
@@ -1,42 +1,51 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_TIMEOUT = 60000;
 MARIONETTE_HEAD_JS = 'head.js';
 
-let Promise = SpecialPowers.Cu.import("resource://gre/modules/Promise.jsm").Promise;
-
-const KEY = "ril.radio.disabled";
-
-let settings;
+let connection;
 let number = "112";
 let outgoing;
 
-function setAirplaneMode() {
-  log("Turning on airplane mode");
+function receivedPending(received, pending, nextAction) {
+  let index = pending.indexOf(received);
+  if (index != -1) {
+    pending.splice(index, 1);
+  }
+  if (pending.length === 0) {
+    nextAction();
+  }
+}
 
-  let deferred = Promise.defer();
+function setRadioEnabled(enabled, callback) {
+  let request  = connection.setRadioEnabled(enabled);
+  let desiredRadioState = enabled ? 'enabled' : 'disabled';
 
-  let setLock = settings.createLock();
-  let obj = {};
-  obj[KEY] = false;
+  let pending = ['onradiostatechange', 'onsuccess'];
+  let done = callback;
 
-  let setReq = setLock.set(obj);
-  setReq.addEventListener("success", function onSetSuccess() {
-    ok(true, "set '" + KEY + "' to " + obj[KEY]);
-    deferred.resolve();
-  });
-  setReq.addEventListener("error", function onSetError() {
-    ok(false, "cannot set '" + KEY + "'");
-    deferred.reject();
-  });
+  connection.onradiostatechange = function() {
+    let state = connection.radioState;
+    log("Received 'radiostatechange' event, radioState: " + state);
 
-  return deferred.promise;
+    if (state == desiredRadioState) {
+      receivedPending('onradiostatechange', pending, done);
+    }
+  };
+
+  request.onsuccess = function onsuccess() {
+    receivedPending('onsuccess', pending, done);
+  };
+
+  request.onerror = function onerror() {
+    ok(false, "setRadioEnabled should be ok");
+  };
 }
 
 function dial() {
   log("Make an outgoing call.");
 
   outgoing = telephony.dial(number);
   ok(outgoing);
   is(outgoing.number, number);
@@ -103,15 +112,17 @@ function hangUp() {
   };
   emulator.run("gsm cancel " + number);
 }
 
 function cleanUp() {
   finish();
 }
 
-startTestWithPermissions(['settings-write'], function() {
-  settings = window.navigator.mozSettings;
-  ok(settings);
-  setAirplaneMode()
-    .then(dial)
-    .then(null, cleanUp);
+startTestWithPermissions(['mobileconnection'], function() {
+  connection = navigator.mozMobileConnections[0];
+  ok(connection instanceof MozMobileConnection,
+     "connection is instanceof " + connection.constructor);
+
+  setRadioEnabled(false, function() {
+    dial();
+  });
 });
--- a/dom/telephony/test/marionette/test_outgoing_radio_off.js
+++ b/dom/telephony/test/marionette/test_outgoing_radio_off.js
@@ -1,45 +1,50 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_TIMEOUT = 60000;
 MARIONETTE_HEAD_JS = 'head.js';
 
-let icc;
 let connection;
 let outgoing;
 
-function changeSetting(key, value, callback) {
-  let obj = {};
-  obj[key] = value;
-
-  let setReq = navigator.mozSettings.createLock().set(obj);
-  setReq.addEventListener("success", function onSetSuccess() {
-    ok(true, "set '" + key + "' to " + obj[key]);
-    setReq.removeEventListener("success", onSetSuccess);
-    callback();
-  });
-  setReq.addEventListener("error", function onSetError() {
-    ok(false, "cannot set '" + key + "'");
-    cleanUp();
-  });
+function receivedPending(received, pending, nextAction) {
+  let index = pending.indexOf(received);
+  if (index != -1) {
+    pending.splice(index, 1);
+  }
+  if (pending.length === 0) {
+    nextAction();
+  }
 }
 
 function setRadioEnabled(enabled, callback) {
-  changeSetting("ril.radio.disabled", !enabled, function() {
-    // Wait for iccdetected event after turning on radio.
-    // Wait for iccundetected event after turning off radio.
-    let event = (enabled) ? "iccdetected" : "iccundetected";
-    icc.addEventListener(event, function handler(evt) {
-      log(event + ": " + evt.iccId);
-      icc.removeEventListener(event, handler);
-      callback();
-    });
-  });
+  let request  = connection.setRadioEnabled(enabled);
+  let desiredRadioState = enabled ? 'enabled' : 'disabled';
+
+  let pending = ['onradiostatechange', 'onsuccess'];
+  let done = callback;
+
+  connection.onradiostatechange = function() {
+    let state = connection.radioState;
+    log("Received 'radiostatechange' event, radioState: " + state);
+
+    if (state == desiredRadioState) {
+      receivedPending('onradiostatechange', pending, done);
+    }
+  };
+
+  request.onsuccess = function onsuccess() {
+    receivedPending('onsuccess', pending, done);
+  };
+
+  request.onerror = function onerror() {
+    ok(false, "setRadioEnabled should be ok");
+  };
 }
 
 function dial(number) {
   // Verify initial state before dial.
   ok(telephony);
   is(telephony.active, null);
   ok(telephony.calls);
   is(telephony.calls.length, 0);
@@ -69,25 +74,17 @@ function dial(number) {
     });
   };
 }
 
 function cleanUp() {
   finish();
 }
 
-let permissions = [
-  "mobileconnection",
-  "settings-write"
-];
-
-startTestWithPermissions(permissions, function() {
+startTestWithPermissions(['mobileconnection'], function() {
   connection = navigator.mozMobileConnections[0];
   ok(connection instanceof MozMobileConnection,
      "connection is instanceof " + connection.constructor);
 
-  icc = navigator.mozIccManager;
-  ok(icc instanceof MozIccManager, "icc is instanceof " + icc.constructor);
-
   setRadioEnabled(false, function() {
     dial("0912345678");
   });
 });
--- a/gfx/layers/composite/APZCTreeManager.cpp
+++ b/gfx/layers/composite/APZCTreeManager.cpp
@@ -208,17 +208,19 @@ APZCTreeManager::UpdatePanZoomController
   if (apzc) {
     aTransform = gfx3DMatrix();
   } else {
     // Multiply child layer transforms on the left so they get applied first
     aTransform = aLayer->GetTransform() * aTransform;
   }
 
   uint64_t childLayersId = (aLayer->AsRefLayer() ? aLayer->AsRefLayer()->GetReferentId() : aLayersId);
-  AsyncPanZoomController* next = nullptr;
+  // If there's no APZC at this level, any APZCs for our child layers will
+  // have our siblings as siblings.
+  AsyncPanZoomController* next = apzc ? nullptr : aNextSibling;
   for (Layer* child = aLayer->GetLastChild(); child; child = child->GetPrevSibling()) {
     next = UpdatePanZoomControllerTree(aCompositor, child, childLayersId, aTransform, aParent, next,
                                        aIsFirstPaint, aFirstPaintLayersId, aApzcsToDestroy);
   }
 
   // Return the APZC that should be the sibling of other APZCs as we continue
   // moving towards the first child at this depth in the layer tree.
   // If this layer doesn't have an APZC, we promote any APZCs in the subtree
--- a/ipc/nfc/Nfc.cpp
+++ b/ipc/nfc/Nfc.cpp
@@ -8,21 +8,21 @@
 
 #include "mozilla/ipc/Nfc.h"
 
 #include <fcntl.h>
 #include <sys/socket.h>
 #include <sys/un.h>
 
 #undef LOG
-#if defined(MOZ_WIDGET_GONK)
+#if (defined(MOZ_WIDGET_GONK) && defined(DEBUG))
 #include <android/log.h>
 #define LOG(args...)  __android_log_print(ANDROID_LOG_INFO, "Gonk", args)
 #else
-#define LOG(args...)  printf(args);
+#define LOG(args...)
 #endif
 
 #include "jsfriendapi.h"
 #include "nsThreadUtils.h" // For NS_IsMainThread.
 
 USING_WORKERS_NAMESPACE
 using namespace mozilla::ipc;
 
--- a/uriloader/exthandler/nsExternalHelperAppService.cpp
+++ b/uriloader/exthandler/nsExternalHelperAppService.cpp
@@ -115,16 +115,20 @@
 
 #include "mozilla/Preferences.h"
 #include "mozilla/ipc/URIUtils.h"
 
 #ifdef MOZ_WIDGET_GONK
 #include "nsDeviceStorage.h"
 #endif
 
+#ifdef NECKO_PROTOCOL_rtsp
+#include "nsISystemMessagesInternal.h"
+#endif
+
 using namespace mozilla;
 using namespace mozilla::ipc;
 
 // Buffer file writes in 32kb chunks
 #define BUFFERED_OUTPUT_SIZE (1024 * 32)
 
 // Download Folder location constants
 #define NS_PREF_DOWNLOAD_DIR        "browser.download.dir"
@@ -577,16 +581,92 @@ nsresult nsExternalHelperAppService::Ini
   NS_ENSURE_SUCCESS(rv, rv);
   return obs->AddObserver(this, "last-pb-context-exited", true);
 }
 
 nsExternalHelperAppService::~nsExternalHelperAppService()
 {
 }
 
+#ifdef NECKO_PROTOCOL_rtsp
+namespace {
+/**
+ * A stack helper to clear the currently pending exception in a JS context.
+ */
+class AutoClearPendingException {
+public:
+  AutoClearPendingException(JSContext* aCx) :
+    mCx(aCx) {
+  }
+  ~AutoClearPendingException() {
+    JS_ClearPendingException(mCx);
+  }
+private:
+  JSContext *mCx;
+};
+} // anonymous namespace
+
+/**
+ * This function broadcasts a system message in order to launch video app for
+ * rtsp scheme. This is Gonk-specific behavior.
+ */
+void nsExternalHelperAppService::LaunchVideoAppForRtsp(nsIURI* aURI)
+{
+  NS_NAMED_LITERAL_STRING(msgType, "rtsp-open-video");
+
+  // Make the url is rtsp.
+  bool isRTSP = false;
+  aURI->SchemeIs("rtsp", &isRTSP);
+  NS_ASSERTION(isRTSP, "Not rtsp protocol! Something goes wrong here");
+
+  // Construct jsval for system message.
+  AutoSafeJSContext cx;
+  AutoClearPendingException helper(cx);
+  JS::Rooted<JSObject*> msgObj(cx, JS_NewObject(cx, nullptr, nullptr, nullptr));
+  NS_ENSURE_TRUE_VOID(msgObj);
+  JS::Rooted<JS::Value> jsVal(cx);
+  bool rv;
+
+  // Set the "url" and "title" properties of the message.
+  // In the case of RTSP streaming, the title is the same as the url.
+  {
+    nsAutoCString spec;
+    aURI->GetAsciiSpec(spec);
+    JSString *urlStr = JS_NewStringCopyN(cx, spec.get(), spec.Length());
+    NS_ENSURE_TRUE_VOID(urlStr);
+    jsVal.setString(urlStr);
+
+    rv = JS_SetProperty(cx, msgObj, "url", jsVal);
+    NS_ENSURE_TRUE_VOID(rv);
+
+    rv = JS_SetProperty(cx, msgObj, "title", jsVal);
+    NS_ENSURE_TRUE_VOID(rv);
+  }
+
+  // Set the "type" property of the message. This is a fake MIME type.
+  {
+    NS_NAMED_LITERAL_CSTRING(mimeType, "video/rtsp");
+    JSString *typeStr = JS_NewStringCopyN(cx, mimeType.get(), mimeType.Length());
+    NS_ENSURE_TRUE_VOID(typeStr);
+    jsVal.setString(typeStr);
+  }
+  rv = JS_SetProperty(cx, msgObj, "type", jsVal);
+  NS_ENSURE_TRUE_VOID(rv);
+
+  // Broadcast system message.
+  nsCOMPtr<nsISystemMessagesInternal> systemMessenger =
+    do_GetService("@mozilla.org/system-message-internal;1");
+  NS_ENSURE_TRUE_VOID(systemMessenger);
+  jsVal.setObject(*msgObj);
+  systemMessenger->BroadcastMessage(msgType, jsVal, JS::UndefinedValue());
+
+  return;
+}
+#endif
+
 NS_IMETHODIMP nsExternalHelperAppService::DoContent(const nsACString& aMimeContentType,
                                                     nsIRequest *aRequest,
                                                     nsIInterfaceRequestor *aWindowContext,
                                                     bool aForceSave,
                                                     nsIStreamListener ** aStreamListener)
 {
   nsAutoString fileName;
   nsAutoCString fileExtension;
@@ -917,17 +997,28 @@ nsExternalHelperAppService::LoadURI(nsIU
       return NS_OK; // missing default pref
     }
   }
 
   if (!allowLoad) {
     return NS_OK; // explicitly denied
   }
 
- 
+#ifdef NECKO_PROTOCOL_rtsp
+  // Handle rtsp protocol.
+  {
+    bool isRTSP = false;
+    rv = aURI->SchemeIs("rtsp", &isRTSP);
+    if (NS_SUCCEEDED(rv) && isRTSP) {
+      LaunchVideoAppForRtsp(aURI);
+      return NS_OK;
+    }
+  }
+#endif
+
   nsCOMPtr<nsIHandlerInfo> handler;
   rv = GetProtocolHandlerInfo(scheme, getter_AddRefs(handler));
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsHandlerInfoAction preferredAction;
   handler->GetPreferredAction(&preferredAction);
   bool alwaysAsk = true;
   handler->GetAlwaysAskBeforeHandling(&alwaysAsk);
--- a/uriloader/exthandler/nsExternalHelperAppService.h
+++ b/uriloader/exthandler/nsExternalHelperAppService.h
@@ -33,16 +33,17 @@
 #include "nsIHandlerService.h"
 #include "nsCOMPtr.h"
 #include "nsIObserver.h"
 #include "nsCOMArray.h"
 #include "nsWeakReference.h"
 #include "nsIPrompt.h"
 #include "nsAutoPtr.h"
 #include "mozilla/Attributes.h"
+#include "necko-config.h"
 
 class nsExternalAppHandler;
 class nsIMIMEInfo;
 class nsITransfer;
 class nsIDOMWindow;
 
 /**
  * The helper app service. Responsible for handling content that Mozilla
@@ -170,16 +171,25 @@ protected:
    */
   void ExpungeTemporaryFiles();
   /**
    * Functions related to the tempory file cleanup service provided by
    * nsExternalHelperAppService (for the temporary files added during
    * the private browsing mode)
    */
   void ExpungeTemporaryPrivateFiles();
+
+#ifdef NECKO_PROTOCOL_rtsp
+  /**
+   * Launch video app for rtsp protocol. This function is supported only on Gonk
+   * for now.
+   */
+  static void LaunchVideoAppForRtsp(nsIURI* aURI);
+#endif
+
   /**
    * Array for the files that should be deleted
    */
   nsCOMArray<nsIFile> mTemporaryFilesList;
   /**
    * Array for the files that should be deleted (for the temporary files
    * added during the private browsing mode)
    */
--- a/widget/gonk/nativewindow/GonkNativeWindowClientJB.cpp
+++ b/widget/gonk/nativewindow/GonkNativeWindowClientJB.cpp
@@ -210,17 +210,17 @@ int GonkNativeWindowClient::dequeueBuffe
 
         if (result != NO_ERROR) {
             ALOGE("dequeueBuffer: requestBuffer failed: %d",
                     result);
             return result;
         }
     }
 
-    if (fence->isValid()) {
+    if (fence.get() && fence->isValid()) {
         *fenceFd = fence->dup();
         if (*fenceFd == -1) {
             ALOGE("dequeueBuffer: error duping fence: %d", errno);
             // dup() should never fail; something is badly wrong. Soldier on
             // and hope for the best; the worst that should happen is some
             // visible corruption that lasts until the next frame.
         }
     } else {