Bug 997064 - Do not call navigator.mozFMRadio from system app. r=baku
authorPin Zhang <pzhang@mozilla.com>
Fri, 25 Apr 2014 17:24:00 -0400
changeset 199871 300930f28f645bfd522a04d4207a1b51e971fc67
parent 199870 a97b0e83340715474d7e02400202a20c597af07c
child 199872 68ce4273bbde2c0b9b029abd8e3fecca55567efd
push id486
push userasasaki@mozilla.com
push dateMon, 14 Jul 2014 18:39:42 +0000
treeherdermozilla-release@d33428174ff1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs997064, 938015
milestone31.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 997064 - Do not call navigator.mozFMRadio from system app. r=baku - Revert "Bug 938015 - Do not turn off FM radio when enabling airplane mode." - Use setting "airplaneMode.enabled" instead of "ril.radio.disabled"
dom/fmradio/FMRadioService.cpp
dom/fmradio/FMRadioService.h
dom/fmradio/test/marionette/manifest.ini
dom/fmradio/test/marionette/test_bug876597.js
--- a/dom/fmradio/FMRadioService.cpp
+++ b/dom/fmradio/FMRadioService.cpp
@@ -19,16 +19,19 @@
 #define BAND_87500_108000_kHz 1
 #define BAND_76000_108000_kHz 2
 #define BAND_76000_90000_kHz  3
 
 #define CHANNEL_WIDTH_200KHZ 200
 #define CHANNEL_WIDTH_100KHZ 100
 #define CHANNEL_WIDTH_50KHZ  50
 
+#define MOZSETTINGS_CHANGED_ID "mozsettings-changed"
+#define SETTING_KEY_AIRPLANEMODE_ENABLED "airplaneMode.enabled"
+
 using namespace mozilla::hal;
 using mozilla::Preferences;
 
 BEGIN_FMRADIO_NAMESPACE
 
 // static
 IFMRadioService*
 IFMRadioService::Singleton()
@@ -40,18 +43,18 @@ IFMRadioService::Singleton()
   }
 }
 
 StaticRefPtr<FMRadioService> FMRadioService::sFMRadioService;
 
 FMRadioService::FMRadioService()
   : mPendingFrequencyInKHz(0)
   , mState(Disabled)
-  , mHasReadRilSetting(false)
-  , mRilDisabled(false)
+  , mHasReadAirplaneModeSetting(false)
+  , mAirplaneModeEnabled(false)
   , mPendingRequest(nullptr)
   , mObserverList(FMRadioEventObserverList())
 {
 
   // Read power state and frequency from Hal.
   mEnabled = IsFMRadioOn();
   if (mEnabled) {
     mPendingFrequencyInKHz = GetFMRadioFrequency();
@@ -85,16 +88,22 @@ FMRadioService::FMRadioService()
     case CHANNEL_WIDTH_100KHZ:
     default:
       mChannelWidthInKHz = 100;
       break;
   }
 
   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
 
+  if (obs && NS_FAILED(obs->AddObserver(this,
+                                        MOZSETTINGS_CHANGED_ID,
+                                        /* useWeak */ false))) {
+    NS_WARNING("Failed to add settings change observer!");
+  }
+
   RegisterFMRadioObserver(this);
 }
 
 FMRadioService::~FMRadioService()
 {
   UnregisterFMRadioObserver(this);
 }
 
@@ -123,41 +132,41 @@ private:
   int32_t mLowerLimit;
   int32_t mSpaceType;
 };
 
 /**
  * Read the airplane-mode setting, if the airplane-mode is not enabled, we
  * enable the FM radio.
  */
-class ReadRilSettingTask MOZ_FINAL : public nsISettingsServiceCallback
+class ReadAirplaneModeSettingTask MOZ_FINAL : public nsISettingsServiceCallback
 {
 public:
   NS_DECL_ISUPPORTS
 
-  ReadRilSettingTask(nsRefPtr<FMRadioReplyRunnable> aPendingRequest)
+  ReadAirplaneModeSettingTask(nsRefPtr<FMRadioReplyRunnable> aPendingRequest)
     : mPendingRequest(aPendingRequest) { }
 
   NS_IMETHOD
   Handle(const nsAString& aName, JS::Handle<JS::Value> aResult)
   {
     FMRadioService* fmRadioService = FMRadioService::Singleton();
     MOZ_ASSERT(mPendingRequest == fmRadioService->mPendingRequest);
 
-    fmRadioService->mHasReadRilSetting = true;
+    fmRadioService->mHasReadAirplaneModeSetting = true;
 
     if (!aResult.isBoolean()) {
       // Failed to read the setting value, set the state back to Disabled.
       fmRadioService->TransitionState(
         ErrorResponse(NS_LITERAL_STRING("Unexpected error")), Disabled);
       return NS_OK;
     }
 
-    fmRadioService->mRilDisabled = aResult.toBoolean();
-    if (!fmRadioService->mRilDisabled) {
+    fmRadioService->mAirplaneModeEnabled = aResult.toBoolean();
+    if (!fmRadioService->mAirplaneModeEnabled) {
       EnableRunnable* runnable =
         new EnableRunnable(fmRadioService->mUpperBoundInKHz,
                            fmRadioService->mLowerBoundInKHz,
                            fmRadioService->mChannelWidthInKHz);
       NS_DispatchToMainThread(runnable);
     } else {
       // Airplane mode is enabled, set the state back to Disabled.
       fmRadioService->TransitionState(ErrorResponse(
@@ -178,17 +187,17 @@ public:
 
     return NS_OK;
   }
 
 private:
   nsRefPtr<FMRadioReplyRunnable> mPendingRequest;
 };
 
-NS_IMPL_ISUPPORTS1(ReadRilSettingTask, nsISettingsServiceCallback)
+NS_IMPL_ISUPPORTS1(ReadAirplaneModeSettingTask, nsISettingsServiceCallback)
 
 class DisableRunnable MOZ_FINAL : public nsRunnable
 {
 public:
   DisableRunnable() { }
 
   NS_IMETHOD Run()
   {
@@ -402,75 +411,82 @@ FMRadioService::Enable(double aFrequency
 
   if (!roundedFrequency) {
     aReplyRunnable->SetReply(ErrorResponse(
       NS_LITERAL_STRING("Frequency is out of range")));
     NS_DispatchToMainThread(aReplyRunnable);
     return;
   }
 
-  if (mHasReadRilSetting && mRilDisabled) {
+  if (mHasReadAirplaneModeSetting && mAirplaneModeEnabled) {
     aReplyRunnable->SetReply(ErrorResponse(
       NS_LITERAL_STRING("Airplane mode currently enabled")));
     NS_DispatchToMainThread(aReplyRunnable);
     return;
   }
 
   SetState(Enabling);
   // Cache the enable request just in case disable() is called
   // while the FM radio HW is being enabled.
   mPendingRequest = aReplyRunnable;
 
   // Cache the frequency value, and set it after the FM radio HW is enabled
   mPendingFrequencyInKHz = roundedFrequency;
 
-  if (!mHasReadRilSetting) {
+  if (!mHasReadAirplaneModeSetting) {
     nsCOMPtr<nsISettingsService> settings =
       do_GetService("@mozilla.org/settingsService;1");
 
     nsCOMPtr<nsISettingsServiceLock> settingsLock;
     nsresult rv = settings->CreateLock(nullptr, getter_AddRefs(settingsLock));
     if (NS_FAILED(rv)) {
       TransitionState(ErrorResponse(
         NS_LITERAL_STRING("Can't create settings lock")), Disabled);
       return;
     }
 
-    nsRefPtr<ReadRilSettingTask> callback =
-      new ReadRilSettingTask(mPendingRequest);
+    nsRefPtr<ReadAirplaneModeSettingTask> callback =
+      new ReadAirplaneModeSettingTask(mPendingRequest);
 
-    rv = settingsLock->Get("ril.radio.disabled", callback);
+    rv = settingsLock->Get(SETTING_KEY_AIRPLANEMODE_ENABLED, callback);
     if (NS_FAILED(rv)) {
       TransitionState(ErrorResponse(
         NS_LITERAL_STRING("Can't get settings lock")), Disabled);
     }
 
     return;
   }
 
   NS_DispatchToMainThread(new EnableRunnable(mUpperBoundInKHz,
                                              mLowerBoundInKHz,
                                              mChannelWidthInKHz));
 }
 
 void
 FMRadioService::Disable(FMRadioReplyRunnable* aReplyRunnable)
 {
+  // When airplane-mode is enabled, we will call this function from
+  // FMRadioService::Observe without passing a FMRadioReplyRunnable,
+  // so we have to check if |aReplyRunnable| is null before we dispatch it.
   MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
 
   switch (mState) {
     case Disabling:
-      aReplyRunnable->SetReply(
-        ErrorResponse(NS_LITERAL_STRING("FM radio currently disabling")));
-      NS_DispatchToMainThread(aReplyRunnable);
+      if (aReplyRunnable) {
+        aReplyRunnable->SetReply(
+          ErrorResponse(NS_LITERAL_STRING("FM radio currently disabling")));
+        NS_DispatchToMainThread(aReplyRunnable);
+      }
       return;
     case Disabled:
-      aReplyRunnable->SetReply(
-        ErrorResponse(NS_LITERAL_STRING("FM radio currently disabled")));
-      NS_DispatchToMainThread(aReplyRunnable);
+      if (aReplyRunnable) {
+        aReplyRunnable->SetReply(
+          ErrorResponse(NS_LITERAL_STRING("FM radio currently disabled")));
+        NS_DispatchToMainThread(aReplyRunnable);
+      }
       return;
     case Enabled:
     case Enabling:
     case Seeking:
       break;
   }
 
   nsRefPtr<FMRadioReplyRunnable> enablingRequest = mPendingRequest;
@@ -489,23 +505,25 @@ FMRadioService::Disable(FMRadioReplyRunn
   if (preState == Enabling) {
     // If the radio is currently enabling, we fire the error callback on the
     // enable request immediately. When the radio finishes enabling, we'll call
     // DoDisable and fire the success callback on the disable request.
     enablingRequest->SetReply(
       ErrorResponse(NS_LITERAL_STRING("Enable action is cancelled")));
     NS_DispatchToMainThread(enablingRequest);
 
-    // If we haven't read the ril settings yet we won't enable the FM radio HW,
-    // so fail the disable request immediately.
-    if (!mHasReadRilSetting) {
+    // If we haven't read the airplane mode settings yet we won't enable the
+    // FM radio HW, so fail the disable request immediately.
+    if (!mHasReadAirplaneModeSetting) {
       SetState(Disabled);
 
-      aReplyRunnable->SetReply(SuccessResponse());
-      NS_DispatchToMainThread(aReplyRunnable);
+      if (aReplyRunnable) {
+        aReplyRunnable->SetReply(SuccessResponse());
+        NS_DispatchToMainThread(aReplyRunnable);
+      }
     }
 
     return;
   }
 
   DoDisable();
 }
 
@@ -626,16 +644,76 @@ FMRadioService::CancelSeek(FMRadioReplyR
 
   TransitionState(
     ErrorResponse(NS_LITERAL_STRING("Seek action is cancelled")), Enabled);
 
   aReplyRunnable->SetReply(SuccessResponse());
   NS_DispatchToMainThread(aReplyRunnable);
 }
 
+NS_IMETHODIMP
+FMRadioService::Observe(nsISupports * aSubject,
+                        const char * aTopic,
+                        const char16_t * aData)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(sFMRadioService);
+
+  if (strcmp(aTopic, MOZSETTINGS_CHANGED_ID) != 0) {
+    return NS_OK;
+  }
+
+  // The string that we're interested in will be a JSON string looks like:
+  //  {"key":"airplaneMode.enabled","value":true}
+  AutoSafeJSContext cx;
+  const nsDependentString dataStr(aData);
+  JS::Rooted<JS::Value> val(cx);
+  if (!JS_ParseJSON(cx, dataStr.get(), dataStr.Length(), &val) ||
+      !val.isObject()) {
+    NS_WARNING("Bad JSON string format.");
+    return NS_OK;
+  }
+
+  JS::Rooted<JSObject*> obj(cx, &val.toObject());
+  JS::Rooted<JS::Value> key(cx);
+  if (!JS_GetProperty(cx, obj, "key", &key) ||
+      !key.isString()) {
+    NS_WARNING("Failed to get string property `key`.");
+    return NS_OK;
+  }
+
+  JS::Rooted<JSString*> jsKey(cx, key.toString());
+  nsDependentJSString keyStr;
+  if (!keyStr.init(cx, jsKey)) {
+    return NS_OK;
+  }
+
+  if (keyStr.EqualsLiteral(SETTING_KEY_AIRPLANEMODE_ENABLED)) {
+    JS::Rooted<JS::Value> value(cx);
+    if (!JS_GetProperty(cx, obj, "value", &value)) {
+      NS_WARNING("Failed to get property `value`.");
+      return NS_OK;
+    }
+
+    if (!value.isBoolean()) {
+      return NS_OK;
+    }
+
+    mAirplaneModeEnabled = value.toBoolean();
+    mHasReadAirplaneModeSetting = true;
+
+    // Disable the FM radio HW if Airplane mode is enabled.
+    if (mAirplaneModeEnabled) {
+      Disable(nullptr);
+    }
+  }
+
+  return NS_OK;
+}
+
 void
 FMRadioService::NotifyFMRadioEvent(FMRadioEventType aType)
 {
   mObserverList.Broadcast(aType);
 }
 
 void
 FMRadioService::Notify(const FMRadioOperationInformation& aInfo)
@@ -727,12 +805,12 @@ FMRadioService::Singleton()
 
   if (!sFMRadioService) {
     sFMRadioService = new FMRadioService();
   }
 
   return sFMRadioService;
 }
 
-NS_IMPL_ISUPPORTS0(FMRadioService)
+NS_IMPL_ISUPPORTS1(FMRadioService, nsIObserver)
 
 END_FMRADIO_NAMESPACE
 
--- a/dom/fmradio/FMRadioService.h
+++ b/dom/fmradio/FMRadioService.h
@@ -8,16 +8,17 @@
 #define mozilla_dom_fmradioservice_h__
 
 #include "mozilla/dom/PFMRadioRequest.h"
 #include "FMRadioCommon.h"
 #include "mozilla/Hal.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/Services.h"
 #include "nsThreadUtils.h"
+#include "nsIObserver.h"
 #include "nsXULAppAPI.h"
 
 BEGIN_FMRADIO_NAMESPACE
 
 class FMRadioReplyRunnable : public nsRunnable
 {
 public:
   FMRadioReplyRunnable() : mResponseType(SuccessResponse()) {}
@@ -131,20 +132,20 @@ enum FMRadioState
   Disabled,
   Disabling,
   Enabling,
   Enabled,
   Seeking
 };
 
 class FMRadioService MOZ_FINAL : public IFMRadioService
-                               , public nsISupports
                                , public hal::FMRadioObserver
+                               , public nsIObserver
 {
-  friend class ReadRilSettingTask;
+  friend class ReadAirplaneModeSettingTask;
   friend class SetFrequencyRunnable;
 
 public:
   static FMRadioService* Singleton();
   virtual ~FMRadioService();
 
   NS_DECL_ISUPPORTS
 
@@ -166,16 +167,18 @@ public:
   virtual void AddObserver(FMRadioEventObserver* aObserver) MOZ_OVERRIDE;
   virtual void RemoveObserver(FMRadioEventObserver* aObserver) MOZ_OVERRIDE;
 
   virtual void EnableAudio(bool aAudioEnabled) MOZ_OVERRIDE;
 
   /* FMRadioObserver */
   void Notify(const hal::FMRadioOperationInformation& aInfo) MOZ_OVERRIDE;
 
+  NS_DECL_NSIOBSERVER
+
 protected:
   FMRadioService();
 
 private:
   int32_t RoundFrequency(double aFrequencyInMHz);
 
   void NotifyFMRadioEvent(FMRadioEventType aType);
   void DoDisable();
@@ -186,18 +189,18 @@ private:
 
 private:
   bool mEnabled;
 
   int32_t mPendingFrequencyInKHz;
 
   FMRadioState mState;
 
-  bool mHasReadRilSetting;
-  bool mRilDisabled;
+  bool mHasReadAirplaneModeSetting;
+  bool mAirplaneModeEnabled;
 
   double mUpperBoundInKHz;
   double mLowerBoundInKHz;
   double mChannelWidthInKHz;
 
   nsRefPtr<FMRadioReplyRunnable> mPendingRequest;
 
   FMRadioEventObserverList mObserverList;
--- a/dom/fmradio/test/marionette/manifest.ini
+++ b/dom/fmradio/test/marionette/manifest.ini
@@ -5,9 +5,10 @@ browser = false
 qemu = false
 
 [test_enable_disable.js]
 [test_set_frequency.js]
 [test_cancel_seek.js]
 [test_one_seek_at_once.js]
 [test_seek_up_and_down.js]
 [test_bug862672.js]
+[test_bug876597.js]
 
new file mode 100644
--- /dev/null
+++ b/dom/fmradio/test/marionette/test_bug876597.js
@@ -0,0 +1,90 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 10000;
+
+SpecialPowers.addPermission("fmradio", true, document);
+SpecialPowers.addPermission("settings-read", true, document);
+SpecialPowers.addPermission("settings-write", true, document);
+
+let FMRadio = window.navigator.mozFMRadio;
+let mozSettings = window.navigator.mozSettings;
+let KEY = "airplaneMode.enabled";
+
+function verifyInitialState() {
+  log("Verifying initial state.");
+  ok(FMRadio);
+  is(FMRadio.enabled, false);
+  ok(mozSettings);
+
+  checkAirplaneModeSettings();
+}
+
+function checkAirplaneModeSettings() {
+  log("Checking airplane mode settings");
+  let req = mozSettings.createLock().get(KEY);
+  req.onsuccess = function(event) {
+    ok(!req.result[KEY], "Airplane mode is disabled.");
+    enableFMRadio();
+  };
+
+  req.onerror = function() {
+    ok(false, "Error occurs when reading settings value.");
+    finish();
+  };
+}
+
+function enableFMRadio() {
+  log("Enable FM radio");
+  let frequency = FMRadio.frequencyLowerBound + FMRadio.channelWidth;
+  let req = FMRadio.enable(frequency);
+
+  req.onsuccess = function() {
+    enableAirplaneMode();
+  };
+
+  req.onerror = function() {
+    ok(false, "Failed to enable FM radio.");
+  };
+}
+
+function enableAirplaneMode() {
+  log("Enable airplane mode");
+  FMRadio.ondisabled = function() {
+    FMRadio.ondisabled = null;
+    enableFMRadioWithAirplaneModeEnabled();
+  };
+
+  let settings = {};
+  settings[KEY] = true;
+  mozSettings.createLock().set(settings);
+}
+
+function enableFMRadioWithAirplaneModeEnabled() {
+  log("Enable FM radio with airplane mode enabled");
+  let frequency = FMRadio.frequencyLowerBound + FMRadio.channelWidth;
+  let req = FMRadio.enable(frequency);
+  req.onerror = cleanUp();
+
+  req.onsuccess = function() {
+    ok(false, "FMRadio could be enabled when airplane mode is enabled.");
+  };
+}
+
+function cleanUp() {
+  let settings = {};
+  settings[KEY] = false;
+  let req = mozSettings.createLock().set(settings);
+
+  req.onsuccess = function() {
+    ok(!FMRadio.enabled);
+    finish();
+  };
+
+  req.onerror = function() {
+    ok(false, "Error occurs when setting value");
+  };
+}
+
+verifyInitialState();
+