diff --git a/b2g/chrome/content/shell.js b/b2g/chrome/content/shell.js index a0479a2b267..aed4811cfa5 100644 --- a/b2g/chrome/content/shell.js +++ b/b2g/chrome/content/shell.js @@ -6,9 +6,6 @@ Cu.import('resource://gre/modules/ContactService.jsm'); Cu.import('resource://gre/modules/SettingsChangeNotifier.jsm'); -#ifdef MOZ_B2G_FM -Cu.import('resource://gre/modules/DOMFMRadioParent.jsm'); -#endif Cu.import('resource://gre/modules/AlarmService.jsm'); Cu.import('resource://gre/modules/ActivitiesService.jsm'); Cu.import('resource://gre/modules/PermissionPromptHelper.jsm'); diff --git a/b2g/installer/package-manifest.in b/b2g/installer/package-manifest.in index 8b03419aef1..97ed83451c2 100644 --- a/b2g/installer/package-manifest.in +++ b/b2g/installer/package-manifest.in @@ -172,9 +172,6 @@ @BINPATH@/components/dom_cellbroadcast.xpt @BINPATH@/components/dom_wappush.xpt #endif -#ifdef MOZ_B2G_FM -@BINPATH@/components/dom_fm.xpt -#endif #ifdef MOZ_B2G_BT @BINPATH@/components/dom_bluetooth.xpt #endif @@ -474,10 +471,6 @@ @BINPATH@/components/NetworkInterfaceListService.manifest @BINPATH@/components/NetworkInterfaceListService.js #endif -#ifdef MOZ_B2G_FM -@BINPATH@/components/DOMFMRadioChild.js -@BINPATH@/components/DOMFMRadio.manifest -#endif #ifdef MOZ_ENABLE_DBUS @BINPATH@/components/@DLL_PREFIX@dbusservice@DLL_SUFFIX@ #endif diff --git a/content/base/src/nsGkAtomList.h b/content/base/src/nsGkAtomList.h index 5a209885095..f1b78c37330 100644 --- a/content/base/src/nsGkAtomList.h +++ b/content/base/src/nsGkAtomList.h @@ -638,6 +638,7 @@ GK_ATOM(onalerting, "onalerting") GK_ATOM(onanimationend, "onanimationend") GK_ATOM(onanimationiteration, "onanimationiteration") GK_ATOM(onanimationstart, "onanimationstart") +GK_ATOM(onantennaavailablechange, "onantennaavailablechange") GK_ATOM(onAppCommand, "onAppCommand") GK_ATOM(onaudioprocess, "onaudioprocess") GK_ATOM(onbeforecopy, "onbeforecopy") @@ -709,6 +710,7 @@ GK_ATOM(onemergencycbmodechange, "onemergencycbmodechange") GK_ATOM(onerror, "onerror") GK_ATOM(onfailed, "onfailed") GK_ATOM(onfocus, "onfocus") +GK_ATOM(onfrequencychange, "onfrequencychange") GK_ATOM(onget, "onget") GK_ATOM(ongroupchange, "ongroupchange") GK_ATOM(onhashchange, "onhashchange") @@ -785,8 +787,6 @@ GK_ATOM(onshow, "onshow") GK_ATOM(onstatechange, "onstatechange") GK_ATOM(onstatuschanged, "onstatuschanged") GK_ATOM(onstkcommand, "onstkcommand") -GK_ATOM(onantennastatechange, "onantennastatechange") -GK_ATOM(onseekcomplete, "onseekcomplete") GK_ATOM(onstksessionend, "onstksessionend") GK_ATOM(onsubmit, "onsubmit") GK_ATOM(onsuccess, "onsuccess") diff --git a/dom/base/Navigator.cpp b/dom/base/Navigator.cpp index cd1948d36b8..f1bdf799410 100644 --- a/dom/base/Navigator.cpp +++ b/dom/base/Navigator.cpp @@ -65,6 +65,10 @@ #include "AudioChannelManager.h" #endif +#ifdef MOZ_B2G_FM +#include "mozilla/dom/FMRadio.h" +#endif + #include "nsIDOMGlobalPropertyInitializer.h" #include "nsJSUtils.h" @@ -195,6 +199,13 @@ Navigator::Invalidate() mBatteryManager = nullptr; } +#ifdef MOZ_B2G_FM + if (mFMRadio) { + mFMRadio->Shutdown(); + mFMRadio = nullptr; + } +#endif + if (mPowerManager) { mPowerManager->Shutdown(); mPowerManager = nullptr; @@ -1047,6 +1058,34 @@ Navigator::GetMozNotification(ErrorResult& aRv) return mNotification; } +#ifdef MOZ_B2G_FM + +using mozilla::dom::FMRadio; + +FMRadio* +Navigator::GetMozFMRadio(ErrorResult& aRv) +{ + if (!mFMRadio) { + if (!mWindow) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + + NS_ENSURE_TRUE(mWindow->GetDocShell(), nullptr); + + mFMRadio = new FMRadio(); + mFMRadio->Init(mWindow); + } + + return mFMRadio; +} + +#endif // MOZ_B2G_FM + +//***************************************************************************** +// Navigator::nsINavigatorBattery +//***************************************************************************** + battery::BatteryManager* Navigator::GetBattery(ErrorResult& aRv) { @@ -1713,6 +1752,16 @@ Navigator::HasBluetoothSupport(JSContext* /* unused */, JSObject* aGlobal) } #endif // MOZ_B2G_BT +#ifdef MOZ_B2G_FM +/* static */ +bool +Navigator::HasFMRadioSupport(JSContext* /* unused */, JSObject* aGlobal) +{ + nsCOMPtr win = GetWindowFromGlobal(aGlobal); + return win && CheckPermission(win, "fmradio"); +} +#endif // MOZ_B2G_FM + #ifdef MOZ_TIME_MANAGER /* static */ bool diff --git a/dom/base/Navigator.h b/dom/base/Navigator.h index 8bc2274c882..bbd1f3fe780 100644 --- a/dom/base/Navigator.h +++ b/dom/base/Navigator.h @@ -52,6 +52,10 @@ namespace battery { class BatteryManager; } // namespace battery +#ifdef MOZ_B2G_FM +class FMRadio; +#endif + class DesktopNotificationCenter; class MobileMessageManager; class MozIdleObserver; @@ -227,6 +231,9 @@ public: #ifdef MOZ_GAMEPAD void GetGamepads(nsTArray >& aGamepads, ErrorResult& aRv); #endif // MOZ_GAMEPAD +#ifdef MOZ_B2G_FM + FMRadio* GetMozFMRadio(ErrorResult& aRv); +#endif #ifdef MOZ_B2G_BT bluetooth::BluetoothManager* GetMozBluetooth(ErrorResult& aRv); #endif // MOZ_B2G_BT @@ -280,6 +287,9 @@ public: #ifdef MOZ_B2G_BT static bool HasBluetoothSupport(JSContext* /* unused */, JSObject* aGlobal); #endif // MOZ_B2G_BT +#ifdef MOZ_B2G_FM + static bool HasFMRadioSupport(JSContext* /* unused */, JSObject* aGlobal); +#endif // MOZ_B2G_FM #ifdef MOZ_TIME_MANAGER static bool HasTimeSupport(JSContext* /* unused */, JSObject* aGlobal); #endif // MOZ_TIME_MANAGER @@ -311,6 +321,9 @@ private: nsRefPtr mGeolocation; nsRefPtr mNotification; nsRefPtr mBatteryManager; +#ifdef MOZ_B2G_FM + nsRefPtr mFMRadio; +#endif nsRefPtr mPowerManager; nsRefPtr mMobileMessageManager; #ifdef MOZ_B2G_RIL diff --git a/dom/base/nsDOMClassInfo.cpp b/dom/base/nsDOMClassInfo.cpp index d9a13ad45ea..2767a5d5db1 100644 --- a/dom/base/nsDOMClassInfo.cpp +++ b/dom/base/nsDOMClassInfo.cpp @@ -541,11 +541,6 @@ static nsDOMClassInfoData sClassInfoData[] = { DOM_DEFAULT_SCRIPTABLE_FLAGS) #endif -#ifdef MOZ_B2G_FM - NS_DEFINE_CLASSINFO_DATA(FMRadio, nsEventTargetSH, - EVENTTARGET_SCRIPTABLE_FLAGS) -#endif - #ifdef MOZ_B2G_BT NS_DEFINE_CLASSINFO_DATA(BluetoothDevice, nsEventTargetSH, EVENTTARGET_SCRIPTABLE_FLAGS) @@ -1412,13 +1407,6 @@ nsDOMClassInfo::Init() #endif -#ifdef MOZ_B2G_FM - DOM_CLASSINFO_MAP_BEGIN(FMRadio, nsIFMRadio) - DOM_CLASSINFO_MAP_ENTRY(nsIFMRadio) - DOM_CLASSINFO_MAP_ENTRY(nsIDOMEventTarget) - DOM_CLASSINFO_MAP_END -#endif - #ifdef MOZ_B2G_BT DOM_CLASSINFO_MAP_BEGIN(BluetoothDevice, nsIDOMBluetoothDevice) DOM_CLASSINFO_MAP_ENTRY(nsIDOMBluetoothDevice) diff --git a/dom/base/nsDOMClassInfoClasses.h b/dom/base/nsDOMClassInfoClasses.h index 67c9c7eff1a..e636165cd0f 100644 --- a/dom/base/nsDOMClassInfoClasses.h +++ b/dom/base/nsDOMClassInfoClasses.h @@ -131,10 +131,6 @@ DOMCI_CLASS(MozVoicemail) DOMCI_CLASS(MozIccManager) #endif -#ifdef MOZ_B2G_FM -DOMCI_CLASS(FMRadio) -#endif - #ifdef MOZ_B2G_BT DOMCI_CLASS(BluetoothDevice) #endif diff --git a/dom/dom-config.mk b/dom/dom-config.mk index 76d11c60b9a..b001fc9d378 100644 --- a/dom/dom-config.mk +++ b/dom/dom-config.mk @@ -48,7 +48,7 @@ endif ifdef MOZ_B2G_FM DOM_SRCDIRS += \ - dom/fm \ + dom/fmradio \ $(NULL) endif diff --git a/dom/fm/DOMFMRadio.manifest b/dom/fm/DOMFMRadio.manifest deleted file mode 100644 index b7d08d26669..00000000000 --- a/dom/fm/DOMFMRadio.manifest +++ /dev/null @@ -1,4 +0,0 @@ -# DOMFMRadio.js -component {901f8a83-03a6-4be9-bb8f-35387d3849da} DOMFMRadioChild.js -contract @mozilla.org/domfmradio;1 {901f8a83-03a6-4be9-bb8f-35387d3849da} -category JavaScript-navigator-property mozFMRadio @mozilla.org/domfmradio;1 diff --git a/dom/fm/DOMFMRadioChild.js b/dom/fm/DOMFMRadioChild.js deleted file mode 100644 index af9699046ee..00000000000 --- a/dom/fm/DOMFMRadioChild.js +++ /dev/null @@ -1,423 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict" -let DEBUG = 0; -if (DEBUG) - debug = function (s) { dump("-*- DOMFMRadioChild: " + s + "\n"); }; -else - debug = function (s) { }; - -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/DOMRequestHelper.jsm"); - -const DOMFMMANAGER_CONTRACTID = "@mozilla.org/domfmradio;1"; -const DOMFMMANAGER_CID = Components.ID("{901f8a83-03a6-4be9-bb8f-35387d3849da}"); - -XPCOMUtils.defineLazyGetter(Services, "DOMRequest", function() { - return Cc["@mozilla.org/dom/dom-request-service;1"] - .getService(Ci.nsIDOMRequestService); -}); - -XPCOMUtils.defineLazyServiceGetter(this, "cpmm", - "@mozilla.org/childprocessmessagemanager;1", - "nsISyncMessageSender"); - -function DOMFMRadioChild() { } - -DOMFMRadioChild.prototype = { - __proto__: DOMRequestIpcHelper.prototype, - - classID: DOMFMMANAGER_CID, - classInfo: XPCOMUtils.generateCI({ - classID: DOMFMMANAGER_CID, - contractID: DOMFMMANAGER_CONTRACTID, - classDescription: "DOMFMRadio", - interfaces: [Ci.nsIDOMFMRadio], - flags: Ci.nsIClassInfo.DOM_OBJECT - }), - - QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMFMRadio, - Ci.nsIDOMGlobalPropertyInitializer, - Ci.nsISupportsWeakReference]), - - // nsIDOMGlobalPropertyInitializer implementation - init: function(aWindow) { - let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"] - .getService(Ci.nsIScriptSecurityManager); - - let perm = Services.perms.testExactPermissionFromPrincipal(aWindow.document.nodePrincipal, "fmradio"); - this._hasPrivileges = perm == Ci.nsIPermissionManager.ALLOW_ACTION; - - if (!this._hasPrivileges) { - Cu.reportError("NO FMRADIO PERMISSION FOR: " + aWindow.document.nodePrincipal.origin + "\n"); - return null; - } - - const messages = ["DOMFMRadio:enable:Return:OK", - "DOMFMRadio:enable:Return:NO", - "DOMFMRadio:disable:Return:OK", - "DOMFMRadio:disable:Return:NO", - "DOMFMRadio:setFrequency:Return:OK", - "DOMFMRadio:setFrequency:Return:NO", - "DOMFMRadio:seekUp:Return:OK", - "DOMFMRadio:seekUp:Return:NO", - "DOMFMRadio:seekDown:Return:OK", - "DOMFMRadio:seekDown:Return:NO", - "DOMFMRadio:cancelSeek:Return:OK", - "DOMFMRadio:cancelSeek:Return:NO", - "DOMFMRadio:frequencyChange", - "DOMFMRadio:powerStateChange", - "DOMFMRadio:antennaChange"]; - this.initDOMRequestHelper(aWindow, messages); - - let els = Cc["@mozilla.org/eventlistenerservice;1"] - .getService(Ci.nsIEventListenerService); - - els.addSystemEventListener(aWindow, "visibilitychange", - this._updateVisibility.bind(this), - /* useCapture = */ true); - - this._visibility = aWindow.document.visibilityState; - // Unlike the |enabled| getter, this is true if *this* DOM window - // has successfully enabled the FM radio more recently than - // disabling it. - this._haveEnabledRadio = false; - }, - - // Called from DOMRequestIpcHelper - uninit: function() { - this._onFrequencyChange = null; - this._onAntennaChange = null; - this._onDisabled = null; - this._onEnabled = null; - }, - - _createEvent: function(name) { - return new this._window.Event(name); - }, - - _sendMessageForRequest: function(name, data, request) { - let id = this.getRequestId(request); - cpmm.sendAsyncMessage(name, { - data: data, - rid: id, - mid: this._id - }); - }, - - _fireFrequencyChangeEvent: function() { - let e = this._createEvent("frequencychange"); - if (this._onFrequencyChange) { - this._onFrequencyChange.handleEvent(e); - } - this.dispatchEvent(e); - }, - - _firePowerStateChangeEvent: function() { - let _enabled = this.enabled; - debug("Current power state: " + _enabled); - if (_enabled) { - let e = this._createEvent("enabled"); - if (this._onEnabled) { - this._onEnabled.handleEvent(e); - } - this.dispatchEvent(e); - } else { - let e = this._createEvent("disabled"); - if (this._onDisabled) { - this._onDisabled.handleEvent(e); - } - this.dispatchEvent(e); - } - }, - - _fireAntennaAvailableChangeEvent: function() { - let e = this._createEvent("antennaavailablechange"); - if (this._onAntennaChange) { - this._onAntennaChange.handleEvent(e); - } - this.dispatchEvent(e); - }, - - _updateVisibility: function(evt) { - this._visibility = evt.target.visibilityState; - // Only notify visibility state when we "own" the radio stream. - if (this._haveEnabledRadio) { - this._notifyVisibility(); - } - }, - - _notifyVisibility: function() { - cpmm.sendAsyncMessage("DOMFMRadio:updateVisibility", this._visibility); - }, - - receiveMessage: function(aMessage) { - let msg = aMessage.json; - if (msg.mid && msg.mid != this._id) { - return; - } - - let request; - switch (aMessage.name) { - case "DOMFMRadio:enable:Return:OK": - request = this.takeRequest(msg.rid); - if (!request) { - return; - } - Services.DOMRequest.fireSuccess(request, null); - break; - case "DOMFMRadio:enable:Return:NO": - request = this.takeRequest(msg.rid); - if (!request) { - return; - } - Services.DOMRequest.fireError(request, "Failed to turn on the FM radio"); - break; - case "DOMFMRadio:disable:Return:OK": - this._haveEnabledRadio = false; - request = this.takeRequest(msg.rid); - if (!request) { - return; - } - Services.DOMRequest.fireSuccess(request, null); - break; - case "DOMFMRadio:disable:Return:NO": - // If disabling the radio failed, but the hardware is still - // on, this DOM window is still responsible for the continued - // playback. - this._haveEnabledRadio = this.enabled; - request = this.takeRequest(msg.rid); - if (!request) { - return; - } - Services.DOMRequest.fireError(request, - "Failed to turn off the FM radio"); - break; - case "DOMFMRadio:setFrequency:Return:OK": - request = this.takeRequest(msg.rid); - if (!request) { - return; - } - Services.DOMRequest.fireSuccess(request, null); - break; - case "DOMFMRadio:setFrequency:Return:NO": - request = this.takeRequest(msg.rid); - if (!request) { - return; - } - Services.DOMRequest.fireError(request, - "Failed to set the FM radio frequency"); - break; - case "DOMFMRadio:seekUp:Return:OK": - request = this.takeRequest(msg.rid); - if (!request) { - return; - } - Services.DOMRequest.fireSuccess(request, null); - break; - case "DOMFMRadio:seekUp:Return:NO": - request = this.takeRequest(msg.rid); - if (!request) { - return; - } - Services.DOMRequest.fireError(request, "FM radio seek-up failed"); - break; - case "DOMFMRadio:seekDown:Return:OK": - request = this.takeRequest(msg.rid); - if (!request) { - return; - } - Services.DOMRequest.fireSuccess(request, null); - break; - case "DOMFMRadio:seekDown:Return:NO": - request = this.takeRequest(msg.rid); - if (!request) { - return; - } - Services.DOMRequest.fireError(request, "FM radio seek-down failed"); - break; - case "DOMFMRadio:cancelSeek:Return:OK": - request = this.takeRequest(msg.rid); - if (!request) { - return; - } - Services.DOMRequest.fireSuccess(request, null); - break; - case "DOMFMRadio:cancelSeek:Return:NO": - request = this.takeRequest(msg.rid); - if (!request) { - return; - } - Services.DOMRequest.fireError(request, "Failed to cancel seek"); - break; - case "DOMFMRadio:powerStateChange": - this._firePowerStateChangeEvent(); - break; - case "DOMFMRadio:frequencyChange": - this._fireFrequencyChangeEvent(); - break; - case "DOMFMRadio:antennaChange": - this._fireAntennaAvailableChangeEvent(); - break; - } - }, - - _call: function(name, arg) { - var request = this.createRequest(); - this._sendMessageForRequest("DOMFMRadio:" + name, arg, request); - return request; - }, - - // nsIDOMFMRadio - get enabled() { - return cpmm.sendSyncMessage("DOMFMRadio:getPowerState")[0]; - }, - - get antennaAvailable() { - return cpmm.sendSyncMessage("DOMFMRadio:getAntennaState")[0]; - }, - - get frequency() { - return cpmm.sendSyncMessage("DOMFMRadio:getFrequency")[0]; - }, - - get frequencyUpperBound() { - let range = cpmm.sendSyncMessage("DOMFMRadio:getCurrentBand")[0]; - return range.upper; - }, - - get frequencyLowerBound() { - let range = cpmm.sendSyncMessage("DOMFMRadio:getCurrentBand")[0]; - return range.lower; - }, - - get channelWidth() { - let range = cpmm.sendSyncMessage("DOMFMRadio:getCurrentBand")[0]; - return range.channelWidth; - }, - - set onantennaavailablechange(callback) { - this._onAntennaChange = callback; - }, - - set onenabled(callback) { - this._onEnabled = callback; - }, - - set ondisabled(callback) { - this._onDisabled = callback; - }, - - set onfrequencychange(callback) { - this._onFrequencyChange = callback; - }, - - disable: function nsIDOMFMRadio_disable() { - return this._call("disable", null); - }, - - enable: function nsIDOMFMRadio_enable(frequency) { - // FMRadio::Enable() needs the most recent visibility state - // synchronously. - this._haveEnabledRadio = true; - this._notifyVisibility(); - return this._call("enable", frequency); - }, - - setFrequency: function nsIDOMFMRadio_setFreq(frequency) { - return this._call("setFrequency", frequency); - }, - - seekDown: function nsIDOMFMRadio_seekDown() { - return this._call("seekDown", null); - }, - - seekUp: function nsIDOMFMRadio_seekUp() { - return this._call("seekUp", null); - }, - - cancelSeek: function nsIDOMFMRadio_cancelSeek() { - return this._call("cancelSeek", null); - }, - - // These are fake implementations, will be replaced by using - // nsJSDOMEventTargetHelper, see bug 731746 - addEventListener: function(type, listener, useCapture) { - if (!this._eventListenersByType) { - this._eventListenersByType = {}; - } - - if (!listener) { - return; - } - - var listeners = this._eventListenersByType[type]; - if (!listeners) { - listeners = this._eventListenersByType[type] = []; - } - - useCapture = !!useCapture; - for (let i = 0, len = listeners.length; i < len; i++) { - let l = listeners[i]; - if (l && l.listener === listener && l.useCapture === useCapture) { - return; - } - } - - listeners.push({ - listener: listener, - useCapture: useCapture - }); - }, - - removeEventListener: function(type, listener, useCapture) { - if (!this._eventListenersByType) { - return; - } - - useCapture = !!useCapture; - - var listeners = this._eventListenersByType[type]; - if (listeners) { - for (let i = 0, len = listeners.length; i < len; i++) { - let l = listeners[i]; - if (l && l.listener === listener && l.useCapture === useCapture) { - listeners.splice(i, 1); - } - } - } - }, - - dispatchEvent: function(evt) { - if (!this._eventListenersByType) { - return; - } - - let type = evt.type; - var listeners = this._eventListenersByType[type]; - if (listeners) { - for (let i = 0, len = listeners.length; i < len; i++) { - let listener = listeners[i].listener; - - try { - if (typeof listener == "function") { - listener.call(this, evt); - } else if (listener && listener.handleEvent && - typeof listener.handleEvent == "function") { - listener.handleEvent(evt); - } - } catch (e) { - debug("Exception is caught: " + e); - } - } - } - } -}; - -this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DOMFMRadioChild]); - diff --git a/dom/fm/DOMFMRadioParent.jsm b/dom/fm/DOMFMRadioParent.jsm deleted file mode 100644 index 41cb4dde5cf..00000000000 --- a/dom/fm/DOMFMRadioParent.jsm +++ /dev/null @@ -1,472 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict" - -let DEBUG = 0; -if (DEBUG) - debug = function(s) { dump("-*- DOMFMRadioParent component: " + s + "\n"); }; -else - debug = function(s) {}; - -const Cu = Components.utils; -const Cc = Components.classes; -const Ci = Components.interfaces; - -const MOZ_SETTINGS_CHANGED_OBSERVER_TOPIC = "mozsettings-changed"; -const PROFILE_BEFORE_CHANGE_OBSERVER_TOPIC = "profile-before-change"; - -const BAND_87500_108000_kHz = 1; -const BAND_76000_108000_kHz = 2; -const BAND_76000_90000_kHz = 3; - -const FM_BANDS = { }; -FM_BANDS[BAND_76000_90000_kHz] = { - lower: 76000, - upper: 90000 -}; - -FM_BANDS[BAND_87500_108000_kHz] = { - lower: 87500, - upper: 108000 -}; - -FM_BANDS[BAND_76000_108000_kHz] = { - lower: 76000, - upper: 108000 -}; - -const BAND_SETTING_KEY = "fmRadio.band"; -const CHANNEL_WIDTH_SETTING_KEY = "fmRadio.channelWidth"; - -// Hal types -const CHANNEL_WIDTH_200KHZ = 200; -const CHANNEL_WIDTH_100KHZ = 100; -const CHANNEL_WIDTH_50KHZ = 50; - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); - -XPCOMUtils.defineLazyServiceGetter(this, "ppmm", - "@mozilla.org/parentprocessmessagemanager;1", - "nsIMessageListenerManager"); - -XPCOMUtils.defineLazyGetter(this, "FMRadio", function() { - return Cc["@mozilla.org/fmradio;1"].getService(Ci.nsIFMRadio); -}); - -XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService", - "@mozilla.org/settingsService;1", - "nsISettingsService"); - -this.EXPORTED_SYMBOLS = ["DOMFMRadioParent"]; - -this.DOMFMRadioParent = { - QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, - Ci.nsISettingsServiceCallback]), - - _initialized: false, - - /* Indicates if the FM radio is currently enabled */ - _isEnabled: false, - - /* Indicates if the FM radio is currently being enabled */ - _enabling: false, - - /* Current frequency in KHz */ - _currentFrequency: 0, - - /* Current band setting */ - _currentBand: BAND_87500_108000_kHz, - - /* Current channel width */ - _currentWidth: CHANNEL_WIDTH_100KHZ, - - /* Indicates if the antenna is currently available */ - _antennaAvailable: true, - - _seeking: false, - - _seekingCallback: null, - - init: function() { - if (this._initialized === true) { - return; - } - this._initialized = true; - - this._messages = ["DOMFMRadio:enable", "DOMFMRadio:disable", - "DOMFMRadio:setFrequency", "DOMFMRadio:getCurrentBand", - "DOMFMRadio:getPowerState", "DOMFMRadio:getFrequency", - "DOMFMRadio:getAntennaState", - "DOMFMRadio:seekUp", "DOMFMRadio:seekDown", - "DOMFMRadio:cancelSeek", - "DOMFMRadio:updateVisibility", - ]; - this._messages.forEach(function(msgName) { - ppmm.addMessageListener(msgName, this); - }.bind(this)); - - Services.obs.addObserver(this, PROFILE_BEFORE_CHANGE_OBSERVER_TOPIC, false); - Services.obs.addObserver(this, MOZ_SETTINGS_CHANGED_OBSERVER_TOPIC, false); - - this._updatePowerState(); - - // Get the band setting and channel width setting - let lock = gSettingsService.createLock(); - lock.get(BAND_SETTING_KEY, this); - lock.get(CHANNEL_WIDTH_SETTING_KEY, this); - - this._updateAntennaState(); - - let self = this; - FMRadio.onantennastatechange = function onantennachange() { - self._updateAntennaState(); - }; - - debug("Initialized"); - }, - - // nsISettingsServiceCallback - handle: function(aName, aResult) { - if (aName == BAND_SETTING_KEY) { - this._updateBand(aResult); - } else if (aName == CHANNEL_WIDTH_SETTING_KEY) { - this._updateChannelWidth(aResult); - } - }, - - handleError: function(aErrorMessage) { - this._updateBand(BAND_87500_108000_kHz); - this._updateChannelWidth(CHANNEL_WIDTH_100KHZ); - }, - - _updateAntennaState: function() { - let antennaState = FMRadio.isAntennaAvailable; - - if (antennaState != this._antennaAvailable) { - this._antennaAvailable = antennaState; - ppmm.broadcastAsyncMessage("DOMFMRadio:antennaChange", { }); - } - }, - - _updateBand: function(band) { - switch (parseInt(band)) { - case BAND_87500_108000_kHz: - case BAND_76000_108000_kHz: - case BAND_76000_90000_kHz: - this._currentBand = band; - break; - } - }, - - _updateChannelWidth: function(channelWidth) { - switch (parseInt(channelWidth)) { - case CHANNEL_WIDTH_50KHZ: - case CHANNEL_WIDTH_100KHZ: - case CHANNEL_WIDTH_200KHZ: - this._currentWidth = channelWidth; - break; - } - }, - - /** - * Update and cache the current frequency. - * Send frequency change message if the frequency is changed. - * The returned boolean value indicates if the frequency is changed. - */ - _updateFrequency: function() { - let frequency = FMRadio.frequency; - - if (frequency != this._currentFrequency) { - this._currentFrequency = frequency; - ppmm.broadcastAsyncMessage("DOMFMRadio:frequencyChange", { }); - return true; - } - - return false; - }, - - /** - * Update and cache the power state of the FM radio. - * Send message if the power state is changed. - */ - _updatePowerState: function() { - let enabled = FMRadio.enabled; - - if (this._isEnabled != enabled) { - this._isEnabled = enabled; - ppmm.broadcastAsyncMessage("DOMFMRadio:powerStateChange", { }); - - // If the FM radio is enabled, update the current frequency immediately, - if (enabled) { - this._updateFrequency(); - } - } - }, - - _onSeekComplete: function(success) { - if (this._seeking) { - this._seeking = false; - - if (this._seekingCallback) { - this._seekingCallback(success); - this._seekingCallback = null; - } - } - }, - - /** - - * Seek the next channel with given direction. - * Only one seek action is allowed at once. - */ - _seekStation: function(direction, aMessage) { - let msg = aMessage.json || { }; - let messageName = aMessage.name + ":Return"; - - // If the FM radio is disabled, do not execute the seek action. - if(!this._isEnabled) { - this._sendMessage(messageName, false, null, msg); - return; - } - - let self = this; - function callback(success) { - debug("Seek completed."); - if (!success) { - self._sendMessage(messageName, false, null, msg); - } else { - // Make sure the FM app will get the right frequency. - self._updateFrequency(); - self._sendMessage(messageName, true, null, msg); - } - } - - if (this._seeking) { - // Pass a boolean value to the callback which indicates that - // the seek action failed. - callback(false); - return; - } - - this._seekingCallback = callback; - this._seeking = true; - - let self = this; - FMRadio.seek(direction); - FMRadio.addEventListener("seekcomplete", function FM_onSeekComplete() { - FMRadio.removeEventListener("seekcomplete", FM_onSeekComplete); - self._onSeekComplete(true); - }); - }, - - /** - * Round the frequency to match the range of frequency and the channel width. - * If the given frequency is out of range, return null. - * For example: - * - lower: 87.5MHz, upper: 108MHz, channel width: 0.2MHz - * 87600 is rounded to 87700 - * 87580 is rounded to 87500 - * 109000 is not rounded, null will be returned - */ - _roundFrequency: function(frequencyInKHz) { - if (frequencyInKHz < FM_BANDS[this._currentBand].lower || - frequencyInKHz > FM_BANDS[this._currentBand].upper) { - return null; - } - - let partToBeRounded = frequencyInKHz - FM_BANDS[this._currentBand].lower; - let roundedPart = Math.round(partToBeRounded / this._currentWidth) * - this._currentWidth; - return FM_BANDS[this._currentBand].lower + roundedPart; - }, - - observe: function(aSubject, aTopic, aData) { - switch (aTopic) { - case PROFILE_BEFORE_CHANGE_OBSERVER_TOPIC: - this._messages.forEach(function(msgName) { - ppmm.removeMessageListener(msgName, this); - }.bind(this)); - - Services.obs.removeObserver(this, PROFILE_BEFORE_CHANGE_OBSERVER_TOPIC); - Services.obs.removeObserver(this, MOZ_SETTINGS_CHANGED_OBSERVER_TOPIC); - - ppmm = null; - this._messages = null; - break; - case MOZ_SETTINGS_CHANGED_OBSERVER_TOPIC: - let setting = JSON.parse(aData); - this.handleMozSettingsChanged(setting); - break; - } - }, - - _sendMessage: function(message, success, data, msg) { - msg.manager.sendAsyncMessage(message + (success ? ":OK" : ":NO"), { - data: data, - rid: msg.rid, - mid: msg.mid - }); - }, - - handleMozSettingsChanged: function(settings) { - switch (settings.key) { - case BAND_SETTING_KEY: - this._updateBand(settings.value); - break; - case CHANNEL_WIDTH_SETTING_KEY: - this._updateChannelWidth(settings.value); - break; - } - }, - - _enableFMRadio: function(msg) { - let frequencyInKHz = this._roundFrequency(msg.data * 1000); - - // If the FM radio is already enabled or it is currently being enabled - // or the given frequency is out of range, return false. - if (this._isEnabled || this._enabling || !frequencyInKHz) { - this._sendMessage("DOMFMRadio:enable:Return", false, null, msg); - return; - } - - this._enabling = true; - let self = this; - - FMRadio.addEventListener("enabled", function on_enabled() { - dump("Perf:FMRadio:Enable " + (Date.now()- timeStart) + " ms.\n"); - self._enabling = false; - - FMRadio.removeEventListener("enabled", on_enabled); - - // To make sure the FM app will get right frequency after the FM - // radio is enabled, we have to set the frequency first. - FMRadio.setFrequency(frequencyInKHz); - - // Update the current frequency without sending 'frequencyChange' - // msg, to make sure the FM app will get the right frequency when the - // 'enabled' event is fired. - self._currentFrequency = FMRadio.frequency; - - self._updatePowerState(); - self._sendMessage("DOMFMRadio:enable:Return", true, null, msg); - - // The frequency is changed from 'null' to some number, so we should - // send the 'frequencyChange' message manually. - ppmm.broadcastAsyncMessage("DOMFMRadio:frequencyChange", { }); - }); - - let timeStart = Date.now(); - - FMRadio.enable({ - lowerLimit: FM_BANDS[self._currentBand].lower, - upperLimit: FM_BANDS[self._currentBand].upper, - channelWidth: self._currentWidth // 100KHz by default - }); - }, - - _disableFMRadio: function(msg) { - // If the FM radio is already disabled, return false. - if (!this._isEnabled) { - this._sendMessage("DOMFMRadio:disable:Return", false, null, msg); - return; - } - - let self = this; - FMRadio.addEventListener("disabled", function on_disabled() { - debug("FM Radio is disabled!"); - FMRadio.removeEventListener("disabled", on_disabled); - - self._updatePowerState(); - self._sendMessage("DOMFMRadio:disable:Return", true, null, msg); - - // If the FM Radio is currently seeking, no fail-to-seek or similar - // event will be fired, execute the seek callback manually. - self._onSeekComplete(false); - }); - - FMRadio.disable(); - }, - - receiveMessage: function(aMessage) { - let msg = aMessage.json || {}; - msg.manager = aMessage.target; - - let ret = 0; - let self = this; - - if (!aMessage.target.assertPermission("fmradio")) { - Cu.reportError("FMRadio message " + aMessage.name + - " from a content process with no 'fmradio' privileges."); - return null; - } - - switch (aMessage.name) { - case "DOMFMRadio:enable": - self._enableFMRadio(msg); - break; - case "DOMFMRadio:disable": - self._disableFMRadio(msg); - break; - case "DOMFMRadio:setFrequency": - let frequencyInKHz = self._roundFrequency(msg.data * 1000); - - // If the FM radio is disabled or the given frequency is out of range, - // skip to set frequency and send back the False message immediately. - if (!self._isEnabled || !frequencyInKHz) { - self._sendMessage("DOMFMRadio:setFrequency:Return", false, null, msg); - } else { - FMRadio.setFrequency(frequencyInKHz); - self._sendMessage("DOMFMRadio:setFrequency:Return", true, null, msg); - this._updateFrequency(); - } - break; - case "DOMFMRadio:getCurrentBand": - // this message is sync - return { - lower: FM_BANDS[self._currentBand].lower / 1000, // in MHz - upper: FM_BANDS[self._currentBand].upper / 1000, // in MHz - channelWidth: self._currentWidth / 1000 // in MHz - }; - case "DOMFMRadio:getPowerState": - // this message is sync - return self._isEnabled; - case "DOMFMRadio:getFrequency": - // this message is sync - return self._isEnabled ? this._currentFrequency / 1000 : null; // in MHz - case "DOMFMRadio:getAntennaState": - // this message is sync - return self._antennaAvailable; - case "DOMFMRadio:seekUp": - self._seekStation(Ci.nsIFMRadio.SEEK_DIRECTION_UP, aMessage); - break; - case "DOMFMRadio:seekDown": - self._seekStation(Ci.nsIFMRadio.SEEK_DIRECTION_DOWN, aMessage); - break; - case "DOMFMRadio:cancelSeek": - // If the FM radio is disabled, or the FM radio is not currently - // seeking, do not execute the cancel seek action. - if (!self._isEnabled || !self._seeking) { - self._sendMessage("DOMFMRadio:cancelSeek:Return", false, null, msg); - } else { - FMRadio.cancelSeek(); - // No fail-to-seek or similar event will be fired from the hal part, - // so execute the seek callback here manually. - this._onSeekComplete(false); - // The FM radio will stop at one frequency without any event, so we need to - // update the current frequency, make sure the FM app will get the right frequency. - this._updateFrequency(); - self._sendMessage("DOMFMRadio:cancelSeek:Return", true, null, msg); - } - break; - case "DOMFMRadio:updateVisibility": - FMRadio.updateVisible(msg == 'visible'); - break; - } - } -}; - -DOMFMRadioParent.init(); - diff --git a/dom/fm/FMRadio.cpp b/dom/fm/FMRadio.cpp deleted file mode 100644 index 4d1b3edc749..00000000000 --- a/dom/fm/FMRadio.cpp +++ /dev/null @@ -1,264 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "mozilla/Hal.h" -#include "mozilla/HalTypes.h" -#include "mozilla/Preferences.h" -#include "nsIAudioManager.h" -#include "FMRadio.h" -#include "nsDOMEvent.h" -#include "nsDOMClassInfo.h" -#include "nsFMRadioSettings.h" -#include "nsCOMPtr.h" - -#undef LOG -#if defined(MOZ_WIDGET_GONK) -#include -#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "FMRadio" , ## args) -#else -#define LOG(args...) -#endif - -// The pref indicates if the device has an internal antenna. -// If the pref is true, the antanna will be always available. -#define DOM_FM_ANTENNA_INTERNAL_PREF "dom.fm.antenna.internal" - -#define RADIO_SEEK_COMPLETE_EVENT_NAME NS_LITERAL_STRING("seekcomplete") -#define RADIO_DISABLED_EVENT_NAME NS_LITERAL_STRING("disabled") -#define RADIO_ENABLED_EVENT_NAME NS_LITERAL_STRING("enabled") -#define ANTENNA_STATE_CHANGED_EVENT_NAME NS_LITERAL_STRING("antennastatechange") - -#define NS_AUDIOMANAGER_CONTRACTID "@mozilla.org/telephony/audiomanager;1" - -using namespace mozilla::dom::fm; -using namespace mozilla::hal; -using mozilla::Preferences; - -FMRadio::FMRadio() - : mHeadphoneState(SWITCH_STATE_OFF) - , mHasInternalAntenna(false) - , mHidden(true) -{ - LOG("FMRadio is initialized."); - - mHasInternalAntenna = Preferences::GetBool(DOM_FM_ANTENNA_INTERNAL_PREF, - /* default = */ false); - if (mHasInternalAntenna) { - LOG("We have an internal antenna."); - } else { - RegisterSwitchObserver(SWITCH_HEADPHONES, this); - mHeadphoneState = GetCurrentSwitchState(SWITCH_HEADPHONES); - } - - RegisterFMRadioObserver(this); -} - -FMRadio::~FMRadio() -{ - UnregisterFMRadioObserver(this); - if (!mHasInternalAntenna) { - UnregisterSwitchObserver(SWITCH_HEADPHONES, this); - } -} - -DOMCI_DATA(FMRadio, FMRadio) - -NS_INTERFACE_MAP_BEGIN(FMRadio) - NS_INTERFACE_MAP_ENTRY(nsIFMRadio) - NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(FMRadio) -NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper) - -NS_IMPL_EVENT_HANDLER(FMRadio, seekcomplete) -NS_IMPL_EVENT_HANDLER(FMRadio, disabled) -NS_IMPL_EVENT_HANDLER(FMRadio, enabled) -NS_IMPL_EVENT_HANDLER(FMRadio, antennastatechange) - -NS_IMPL_ADDREF_INHERITED(FMRadio, nsDOMEventTargetHelper) -NS_IMPL_RELEASE_INHERITED(FMRadio, nsDOMEventTargetHelper) - -/* readonly attribute boolean isAntennaAvailable; */ -NS_IMETHODIMP FMRadio::GetIsAntennaAvailable(bool *aIsAvailable) -{ - if (mHasInternalAntenna) { - *aIsAvailable = true; - } else { - *aIsAvailable = mHeadphoneState != SWITCH_STATE_OFF; - } - return NS_OK; -} - -/* readonly attribute long frequency; */ -NS_IMETHODIMP FMRadio::GetFrequency(int32_t *aFrequency) -{ - *aFrequency = GetFMRadioFrequency(); - return NS_OK; -} - -/* readonly attribute blean enabled; */ -NS_IMETHODIMP FMRadio::GetEnabled(bool *aEnabled) -{ - *aEnabled = IsFMRadioOn(); - return NS_OK; -} - -/* void enable (in nsIFMRadioSettings settings); */ -NS_IMETHODIMP FMRadio::Enable(nsIFMRadioSettings *settings) -{ - hal::FMRadioSettings info; - - int32_t upperLimit, lowerLimit, channelWidth; - - if (!mAudioChannelAgent) { - nsresult rv; - mAudioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1", &rv); - if (!mAudioChannelAgent) { - return NS_ERROR_FAILURE; - } - mAudioChannelAgent->Init(AUDIO_CHANNEL_CONTENT, this); - } - - bool canPlay; - mAudioChannelAgent->SetVisibilityState(!mHidden); - mAudioChannelAgent->StartPlaying(&canPlay); - - settings->GetUpperLimit(&upperLimit); - settings->GetLowerLimit(&lowerLimit); - settings->GetChannelWidth(&channelWidth); - - info.upperLimit() = upperLimit; - info.lowerLimit() = lowerLimit; - info.spaceType() = channelWidth; - - EnableFMRadio(info); - - nsCOMPtr audioManager = - do_GetService(NS_AUDIOMANAGER_CONTRACTID); - NS_ENSURE_TRUE(audioManager, NS_OK); - - audioManager->SetFmRadioAudioEnabled(true); - // We enable the hardware, but mute the audio stream, in order to - // simplify state handling. This is simpler but worse for battery - // life; followup is bug 820282. - // Note: To adjust FM volume is only available after setting up - // routing patch. - CanPlayChanged(canPlay); - - return NS_OK; -} - -/* void disableRadio (); */ -NS_IMETHODIMP FMRadio::Disable() -{ - // Fix Bug 796733. - // DisableFMRadio should be called before SetFmRadioAudioEnabled to prevent - // the annoying beep sound. - DisableFMRadio(); - - nsCOMPtr audioManager = - do_GetService(NS_AUDIOMANAGER_CONTRACTID); - NS_ENSURE_TRUE(audioManager, NS_OK); - - audioManager->SetFmRadioAudioEnabled(false); - - if (mAudioChannelAgent) { - mAudioChannelAgent->StopPlaying(); - mAudioChannelAgent = nullptr; - } - return NS_OK; -} - -/* void cancelSeek */ -NS_IMETHODIMP FMRadio::CancelSeek() -{ - CancelFMRadioSeek(); - return NS_OK; -} - -/* void seek (in long direction); */ -NS_IMETHODIMP FMRadio::Seek(int32_t direction) -{ - if (direction == (int)FM_RADIO_SEEK_DIRECTION_UP) { - FMRadioSeek(FM_RADIO_SEEK_DIRECTION_UP); - } else { - FMRadioSeek(FM_RADIO_SEEK_DIRECTION_DOWN); - } - return NS_OK; -} - -/* nsIFMRadioSettings getSettings (); */ -NS_IMETHODIMP FMRadio::GetSettings(nsIFMRadioSettings * *_retval) -{ - hal::FMRadioSettings settings; - GetFMRadioSettings(&settings); - - nsCOMPtr radioSettings(new nsFMRadioSettings( - settings.upperLimit(), - settings.lowerLimit(), - settings.spaceType())); - radioSettings.forget(_retval); - - return NS_OK; -} - -/* void setFrequency (in long frequency); */ -NS_IMETHODIMP FMRadio::SetFrequency(int32_t frequency) -{ - SetFMRadioFrequency(frequency); - return NS_OK; -} - -NS_IMETHODIMP FMRadio::UpdateVisible(bool aVisible) -{ - mHidden = !aVisible; - if (mAudioChannelAgent) { - mAudioChannelAgent->SetVisibilityState(!mHidden); - } - return NS_OK; -} - -void FMRadio::Notify(const SwitchEvent& aEvent) -{ - if (mHeadphoneState != aEvent.status()) { - LOG("Antenna state is changed!"); - mHeadphoneState = aEvent.status(); - DispatchTrustedEvent(ANTENNA_STATE_CHANGED_EVENT_NAME); - } -} - -void FMRadio::Notify(const FMRadioOperationInformation& info) -{ - switch (info.operation()) - { - case FM_RADIO_OPERATION_ENABLE: - DispatchTrustedEvent(RADIO_ENABLED_EVENT_NAME); - break; - case FM_RADIO_OPERATION_DISABLE: - DispatchTrustedEvent(RADIO_DISABLED_EVENT_NAME); - break; - case FM_RADIO_OPERATION_SEEK: - DispatchTrustedEvent(RADIO_SEEK_COMPLETE_EVENT_NAME); - break; - default: - MOZ_CRASH(); - } -} - -/* void canPlayChanged (in boolean canPlay); */ -NS_IMETHODIMP FMRadio::CanPlayChanged(bool canPlay) -{ - nsCOMPtr audioManager = - do_GetService(NS_AUDIOMANAGER_CONTRACTID); - NS_ENSURE_TRUE(audioManager, NS_OK); - - bool AudioEnabled; - audioManager->GetFmRadioAudioEnabled(&AudioEnabled); - if (AudioEnabled == canPlay) { - return NS_OK; - } - - /* mute fm first, it should be better to stop&resume fm */ - audioManager->SetFmRadioAudioEnabled(canPlay); - return NS_OK; -} - diff --git a/dom/fm/FMRadio.h b/dom/fm/FMRadio.h deleted file mode 100644 index 19977bfb84a..00000000000 --- a/dom/fm/FMRadio.h +++ /dev/null @@ -1,55 +0,0 @@ -/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ -/* vim: set ts=2 et sw=2 tw=40: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef mozilla_dom_fm_radio_h__ -#define mozilla_dom_fm_radio_h__ - -#include "nsCOMPtr.h" -#include "mozilla/HalTypes.h" -#include "nsDOMEventTargetHelper.h" -#include "nsIFMRadio.h" -#include "AudioChannelService.h" - -#define NS_FMRADIO_CONTRACTID "@mozilla.org/fmradio;1" -// 9cb91834-78a9-4029-b644-7806173c5e2d -#define NS_FMRADIO_CID {0x9cb91834, 0x78a9, 0x4029, \ - {0xb6, 0x44, 0x78, 0x06, 0x17, 0x3c, 0x5e, 0x2d}} - -namespace mozilla { -namespace dom { -namespace fm { - -/* Header file */ -class FMRadio : public nsDOMEventTargetHelper - , public nsIFMRadio - , public hal::FMRadioObserver - , public hal::SwitchObserver - , public nsIAudioChannelAgentCallback -{ -public: - NS_DECL_ISUPPORTS_INHERITED - NS_DECL_NSIFMRADIO - NS_DECL_NSIAUDIOCHANNELAGENTCALLBACK - - NS_REALLY_FORWARD_NSIDOMEVENTTARGET(nsDOMEventTargetHelper) - FMRadio(); - virtual void Notify(const hal::FMRadioOperationInformation& info); - virtual void Notify(const hal::SwitchEvent& aEvent); - -private: - ~FMRadio(); - - nsCOMPtr mAudioChannelAgent; - hal::SwitchState mHeadphoneState; - bool mHasInternalAntenna; - bool mHidden; -}; - -} // namespace fm -} // namespace dom -} // namespace mozilla -#endif - diff --git a/dom/fm/moz.build b/dom/fm/moz.build deleted file mode 100644 index 1fe739b8c85..00000000000 --- a/dom/fm/moz.build +++ /dev/null @@ -1,31 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -XPIDL_SOURCES += [ - 'nsIDOMFMRadio.idl', - 'nsIFMRadio.idl', -] - -XPIDL_MODULE = 'dom_fm' - -MODULE = 'dom' - -CPP_SOURCES += [ - 'FMRadio.cpp', - 'nsFMRadioSettings.cpp', -] - -EXTRA_JS_MODULES += [ - 'DOMFMRadioParent.jsm', -] - -EXTRA_COMPONENTS += [ - 'DOMFMRadio.manifest', - 'DOMFMRadioChild.js', -] - -LIBXUL_LIBRARY = True - diff --git a/dom/fm/nsFMRadioSettings.cpp b/dom/fm/nsFMRadioSettings.cpp deleted file mode 100644 index 0d6b167a2e4..00000000000 --- a/dom/fm/nsFMRadioSettings.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "nsFMRadioSettings.h" - -NS_IMPL_ISUPPORTS1(nsFMRadioSettings, nsIFMRadioSettings) - -nsFMRadioSettings::nsFMRadioSettings(int32_t aUpperLimit, - int32_t aLowerLimit, - int32_t aChannelWidth) -{ - mUpperLimit = aUpperLimit; - mLowerLimit = aLowerLimit; - mChannelWidth = aChannelWidth; -} - -nsFMRadioSettings::~nsFMRadioSettings() -{ - -} - -/* attribute long upperLimit; */ -NS_IMETHODIMP nsFMRadioSettings::GetUpperLimit(int32_t *aUpperLimit) -{ - *aUpperLimit = mUpperLimit; - return NS_OK; -} - -NS_IMETHODIMP nsFMRadioSettings::SetUpperLimit(int32_t aUpperLimit) -{ - mUpperLimit = aUpperLimit; - return NS_OK; -} - -/* attribute long lowerLimit; */ -NS_IMETHODIMP nsFMRadioSettings::GetLowerLimit(int32_t *aLowerLimit) -{ - *aLowerLimit = mLowerLimit; - return NS_OK; -} - -NS_IMETHODIMP nsFMRadioSettings::SetLowerLimit(int32_t aLowerLimit) -{ - mLowerLimit = aLowerLimit; - return NS_OK; -} - -/* attribute long spaceType; */ -NS_IMETHODIMP nsFMRadioSettings::GetChannelWidth(int32_t *aChannelWidth) -{ - *aChannelWidth = mChannelWidth; - return NS_OK; -} - -NS_IMETHODIMP nsFMRadioSettings::SetChannelWidth(int32_t aChannelWidth) -{ - mChannelWidth = aChannelWidth; - return NS_OK; -} - diff --git a/dom/fm/nsFMRadioSettings.h b/dom/fm/nsFMRadioSettings.h deleted file mode 100644 index aa6fe83332b..00000000000 --- a/dom/fm/nsFMRadioSettings.h +++ /dev/null @@ -1,26 +0,0 @@ -/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ -/* vim: set ts=2 et sw=2 tw=40: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef mozilla_dom_fm_radio_settings_h__ -#define mozilla_dom_fm_radio_settings_h__ - -#include "nsIFMRadio.h" - -class nsFMRadioSettings : public nsIFMRadioSettings -{ -public: - NS_DECL_ISUPPORTS - NS_DECL_NSIFMRADIOSETTINGS - - nsFMRadioSettings(int32_t aUpperLimit, int32_t aLowerLimit, int32_t aChannelWidth); -private: - ~nsFMRadioSettings(); - int32_t mUpperLimit; - int32_t mLowerLimit; - int32_t mChannelWidth; -}; -#endif - diff --git a/dom/fm/nsIDOMFMRadio.idl b/dom/fm/nsIDOMFMRadio.idl deleted file mode 100644 index d493ac28cde..00000000000 --- a/dom/fm/nsIDOMFMRadio.idl +++ /dev/null @@ -1,122 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "nsISupports.idl" -#include "nsIDOMDOMRequest.idl" - -[scriptable, uuid(1d0443f3-ac30-4f9e-a070-002bb20ce1e6)] -interface nsIDOMFMRadio : nsISupports { - /* Indicates if the FM radio is enabled. */ - readonly attribute boolean enabled; - - /* Indicates if the antenna is plugged and available. */ - readonly attribute boolean antennaAvailable; - - /** - * Current frequency in MHz. - * The value will be null if the FM radio is disabled. - */ - readonly attribute jsval frequency; - - /* The upper bound of frequency in MHz. */ - readonly attribute double frequencyUpperBound; - - /* The lower bound of frequency in MHz. */ - readonly attribute double frequencyLowerBound; - - /** - * The channel width of the ranges of frequency, in MHz. - * Usually, the value is one of: - * - 0.05 MHz - * - 0.1 MHz - * - 0.2 MHz - */ - readonly attribute double channelWidth; - - /* Fired when the FM radio is enabled. */ - attribute nsIDOMEventListener onenabled; - - /* Fired when the FM radio is disabled. */ - attribute nsIDOMEventListener ondisabled; - - /** - * Fired when the antenna becomes available or unavailable, i.e., fired when - * the antennaAvailable attribute changes. - */ - attribute nsIDOMEventListener onantennaavailablechange; - - /* Fired when the FM radio's frequency is changed. */ - attribute nsIDOMEventListener onfrequencychange; - - /** - * Power the FM radio off. - * The disabled event will be fired if this request completes successfully. - */ - nsIDOMDOMRequest disable(); - - /** - * Power the FM radio on, and tune the radio to the given frequency in MHz. - * This will fail if the given frequency is out of range. - * The enabled event and frequencychange event will be fired if this request - * completes successfully. - */ - nsIDOMDOMRequest enable(in double frequency); - - /** - * Tune the FM radio to the given frequency. - * This will fail if the given frequency is out of range. - * - * Note that the FM radio may not tuned to the exact frequency given. To get - * the frequency the radio is actually tuned to, wait for the request to fire - * onsucess (or wait for the frequencychange event to fire), and then read the - * frequency attribute. - */ - nsIDOMDOMRequest setFrequency(in double frequency); - - /** - * Tell the FM radio to seek up to the next channel. If the frequency is - * successfully changed, the frequencychange event will be triggered. - * - * Only one seek is allowed at once: - * If the radio is seeking when the seekUp is called, onerror will be fired. - */ - nsIDOMDOMRequest seekUp(); - - /** - * Tell the FM radio to seek down to the next channel. If the frequency is - * successfully changed, the frequencychange event will be triggered. - * - * Only one seek is allowed at once: - * If the radio is seeking when the seekDown is called, onerror will be fired. - */ - nsIDOMDOMRequest seekDown(); - - /** - * Cancel the seek action. - * If the radio is not currently seeking up or down, onerror will be fired. - */ - nsIDOMDOMRequest cancelSeek(); - - - /** - * These functions related to EventTarget are temporary hacks: - * - addEventListener - * - removeEventListener - * - handleEvent - * - * These will be removed by inheriting from nsIJSDOMEventTarget, - * see bug 731746. - */ - [optional_argc] void addEventListener(in DOMString type, - in nsIDOMEventListener listener, - [optional] in boolean useCapture, - [optional] in boolean wantsUntrusted); - - void removeEventListener(in DOMString type, - in nsIDOMEventListener listener, - [optional] in boolean useCapture); - - boolean dispatchEvent(in nsIDOMEvent evt) raises(DOMException); -}; - diff --git a/dom/fm/nsIFMRadio.idl b/dom/fm/nsIFMRadio.idl deleted file mode 100644 index e4abd6352e3..00000000000 --- a/dom/fm/nsIFMRadio.idl +++ /dev/null @@ -1,107 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "nsIDOMEventTarget.idl" - -[scriptable, uuid(c142387a-5488-454b-8b5a-91f0dbee833b)] -interface nsIFMRadioSettings : nsISupports -{ - /* Upper limit in KHz */ - attribute long upperLimit; - /* Lower limit in KHz */ - attribute long lowerLimit; - /* Channel width in KHz */ - attribute long channelWidth; -}; - -/** - * This is an interface to expose the FM radio hardware related functions; - * it's kind of the FM radio hardware wrapper interface. - * - * Because the WebFM API (navigator.mozFMRadio) is implemented as a JS component, - * it can't access our C++ hardware interface directly; instead it must go - * through this interface. - * Do not confuse this interface with the WebFM DOM interface (nsIDOMFMRadio). - * - * If the WebFM API is re-written in c++ some day, this interface will be useless. - */ -[scriptable, builtinclass, uuid(2ee7c122-b7aa-4948-9bc5-e4593ed4ac32)] -interface nsIFMRadio : nsIDOMEventTarget { - const long SEEK_DIRECTION_UP = 0; - const long SEEK_DIRECTION_DOWN = 1; - - /** - * Indicates if the FM radio hardware is enabled. - */ - readonly attribute boolean enabled; - - /** - * Current frequency in KHz - */ - readonly attribute long frequency; - - /** - * Indicates if the antenna is plugged in and available. - */ - readonly attribute boolean isAntennaAvailable; - - /** - * Enable the FM radio hardware with the given settings. - */ - void enable(in nsIFMRadioSettings settings); - - /** - * Disable the FM radio hardware. - */ - void disable(); - - /** - * Seek the next available channel (up or down). - * - * @param direction - * The value should be one of SEEK_DIRECTION_DOWN and SEEK_DIRECTION_UP - */ - void seek(in long direction); - - /** - * Cancel the seek action. - */ - void cancelSeek(); - - /** - * Get the current settings. - */ - nsIFMRadioSettings getSettings(); - - /** - * Set the frequency in KHz - */ - void setFrequency(in long frequency); - - /** - * Update the visibility state of our client. - */ - void updateVisible(in boolean visible); - - /** - * Fired when the antenna state is changed. - */ - [implicit_jscontext] attribute jsval onantennastatechange; - - /** - * Fired when a seek action completes. - */ - [implicit_jscontext] attribute jsval onseekcomplete; - - /** - * Fired when the FM radio hardware is enabled. - */ - [implicit_jscontext] attribute jsval onenabled; - - /** - * Fired when the FM radio hardware is disabled. - */ - [implicit_jscontext] attribute jsval ondisabled; -}; - diff --git a/dom/fmradio/FMRadio.cpp b/dom/fmradio/FMRadio.cpp new file mode 100644 index 00000000000..57134dee573 --- /dev/null +++ b/dom/fmradio/FMRadio.cpp @@ -0,0 +1,295 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/FMRadio.h" +#include "nsContentUtils.h" +#include "mozilla/Hal.h" +#include "mozilla/HalTypes.h" +#include "mozilla/Preferences.h" +#include "mozilla/dom/FMRadioBinding.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/PFMRadioChild.h" +#include "mozilla/dom/FMRadioService.h" +#include "DOMRequest.h" + +#undef LOG +#define LOG(args...) FM_LOG("FMRadio", args) + +// The pref indicates if the device has an internal antenna. +// If the pref is true, the antanna will be always available. +#define DOM_FM_ANTENNA_INTERNAL_PREF "dom.fmradio.antenna.internal" + +using namespace mozilla::hal; +using mozilla::Preferences; + +BEGIN_FMRADIO_NAMESPACE + +class FMRadioRequest MOZ_FINAL : public ReplyRunnable + , public DOMRequest +{ +public: + NS_DECL_ISUPPORTS_INHERITED + + FMRadioRequest(nsPIDOMWindow* aWindow, FMRadio* aFMRadio) + : DOMRequest(aWindow) + { + // |FMRadio| inherits from |nsIDOMEventTarget| and |nsISupportsWeakReference| + // which both inherits from nsISupports, so |nsISupports| is an ambiguous + // base of |FMRadio|, we have to cast |aFMRadio| to one of the base classes. + mFMRadio = do_GetWeakReference(static_cast(aFMRadio)); + } + + ~FMRadioRequest() { } + + NS_IMETHODIMP + Run() + { + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); + + nsCOMPtr target = do_QueryReferent(mFMRadio); + if (!target) { + return NS_OK; + } + + FMRadio* fmRadio = static_cast( + static_cast(target)); + + if (fmRadio->mIsShutdown) { + return NS_OK; + } + + switch (mResponseType.type()) { + case FMRadioResponseType::TErrorResponse: + FireError(mResponseType.get_ErrorResponse().error()); + break; + case FMRadioResponseType::TSuccessResponse: + FireSuccess(JS::UndefinedHandleValue); + break; + default: + MOZ_CRASH(); + } + + return NS_OK; + } + +private: + nsWeakPtr mFMRadio; +}; + +NS_IMPL_ISUPPORTS_INHERITED0(FMRadioRequest, DOMRequest) + +FMRadio::FMRadio() + : mHeadphoneState(SWITCH_STATE_OFF) + , mHasInternalAntenna(false) + , mIsShutdown(false) +{ + LOG("FMRadio is initialized."); + + SetIsDOMBinding(); +} + +FMRadio::~FMRadio() +{ +} + +void +FMRadio::Init(nsPIDOMWindow *aWindow) +{ + BindToOwner(aWindow); + + IFMRadioService::Singleton()->AddObserver(this); + + mHasInternalAntenna = Preferences::GetBool(DOM_FM_ANTENNA_INTERNAL_PREF, + /* default = */ false); + if (mHasInternalAntenna) { + LOG("We have an internal antenna."); + } else { + mHeadphoneState = GetCurrentSwitchState(SWITCH_HEADPHONES); + RegisterSwitchObserver(SWITCH_HEADPHONES, this); + } +} + +void +FMRadio::Shutdown() +{ + IFMRadioService::Singleton()->RemoveObserver(this); + + if (!mHasInternalAntenna) { + UnregisterSwitchObserver(SWITCH_HEADPHONES, this); + } + + mIsShutdown = true; +} + +JSObject* +FMRadio::WrapObject(JSContext* aCx, JS::Handle aScope) +{ + return FMRadioBinding::Wrap(aCx, aScope, this); +} + +void +FMRadio::Notify(const SwitchEvent& aEvent) +{ + MOZ_ASSERT(!mHasInternalAntenna); + + if (mHeadphoneState != aEvent.status()) { + mHeadphoneState = aEvent.status(); + + DispatchTrustedEvent(NS_LITERAL_STRING("antennaavailablechange")); + } +} + +void +FMRadio::Notify(const FMRadioEventType& aType) +{ + switch (aType) { + case FrequencyChanged: + DispatchTrustedEvent(NS_LITERAL_STRING("frequencychange")); + break; + case EnabledChanged: + if (Enabled()) { + DispatchTrustedEvent(NS_LITERAL_STRING("enabled")); + } else { + DispatchTrustedEvent(NS_LITERAL_STRING("disabled")); + } + break; + default: + MOZ_CRASH(); + } +} + +/* static */ +bool +FMRadio::Enabled() +{ + return IFMRadioService::Singleton()->IsEnabled(); +} + +bool +FMRadio::AntennaAvailable() const +{ + return mHasInternalAntenna ? true : mHeadphoneState != SWITCH_STATE_OFF; +} + +Nullable +FMRadio::GetFrequency() const +{ + return Enabled() ? + Nullable(IFMRadioService::Singleton()->GetFrequency()) : + Nullable(); +} + +double +FMRadio::FrequencyUpperBound() const +{ + return IFMRadioService::Singleton()->GetFrequencyUpperBound(); +} + +double +FMRadio::FrequencyLowerBound() const +{ + return IFMRadioService::Singleton()->GetFrequencyLowerBound(); +} + +double +FMRadio::ChannelWidth() const +{ + return IFMRadioService::Singleton()->GetChannelWidth(); +} + +already_AddRefed +FMRadio::Enable(double aFrequency) +{ + nsCOMPtr win = GetOwner(); + if (!win) { + return nullptr; + } + + nsRefPtr r = new FMRadioRequest(win, this); + IFMRadioService::Singleton()->Enable(aFrequency, r); + + return r.forget(); +} + +already_AddRefed +FMRadio::Disable() +{ + nsCOMPtr win = GetOwner(); + if (!win) { + return nullptr; + } + + nsRefPtr r = new FMRadioRequest(win, this); + IFMRadioService::Singleton()->Disable(r); + + return r.forget(); +} + +already_AddRefed +FMRadio::SetFrequency(double aFrequency) +{ + nsCOMPtr win = GetOwner(); + if (!win) { + return nullptr; + } + + nsRefPtr r = new FMRadioRequest(win, this); + IFMRadioService::Singleton()->SetFrequency(aFrequency, r); + + return r.forget(); +} + +already_AddRefed +FMRadio::SeekUp() +{ + nsCOMPtr win = GetOwner(); + if (!win) { + return nullptr; + } + + nsRefPtr r = new FMRadioRequest(win, this); + IFMRadioService::Singleton()->Seek(FM_RADIO_SEEK_DIRECTION_UP, r); + + return r.forget(); +} + +already_AddRefed +FMRadio::SeekDown() +{ + nsCOMPtr win = GetOwner(); + if (!win) { + return nullptr; + } + + nsRefPtr r = new FMRadioRequest(win, this); + IFMRadioService::Singleton()->Seek(FM_RADIO_SEEK_DIRECTION_DOWN, r); + + return r.forget(); +} + +already_AddRefed +FMRadio::CancelSeek() +{ + nsCOMPtr win = GetOwner(); + if (!win) { + return nullptr; + } + + nsRefPtr r = new FMRadioRequest(win, this); + IFMRadioService::Singleton()->CancelSeek(r); + + return r.forget(); +} + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(FMRadio) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) +NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper) + +NS_IMPL_ADDREF_INHERITED(FMRadio, nsDOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(FMRadio, nsDOMEventTargetHelper) + +END_FMRADIO_NAMESPACE + diff --git a/dom/fmradio/FMRadio.h b/dom/fmradio/FMRadio.h new file mode 100644 index 00000000000..a4cf8293034 --- /dev/null +++ b/dom/fmradio/FMRadio.h @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_FMRadio_h +#define mozilla_dom_FMRadio_h + +#include "FMRadioCommon.h" +#include "nsDOMEventTargetHelper.h" +#include "nsCycleCollectionParticipant.h" +#include "mozilla/HalTypes.h" +#include "nsWeakReference.h" + +class nsPIDOMWindow; +class nsIScriptContext; + +BEGIN_FMRADIO_NAMESPACE + +class DOMRequest; + +class FMRadio MOZ_FINAL : public nsDOMEventTargetHelper + , public hal::SwitchObserver + , public FMRadioEventObserver + , public nsSupportsWeakReference +{ + friend class FMRadioRequest; + +public: + FMRadio(); + + NS_DECL_ISUPPORTS_INHERITED + + NS_REALLY_FORWARD_NSIDOMEVENTTARGET(nsDOMEventTargetHelper) + + void Init(nsPIDOMWindow *aWindow); + void Shutdown(); + + /* hal::SwitchObserver */ + virtual void Notify(const hal::SwitchEvent& aEvent) MOZ_OVERRIDE; + /* FMRadioEventObserver */ + virtual void Notify(const FMRadioEventType& aType) MOZ_OVERRIDE; + + nsPIDOMWindow* GetParentObject() const + { + return GetOwner(); + } + + virtual JSObject* WrapObject(JSContext* aCx, + JS::Handle aScope) MOZ_OVERRIDE; + + static bool Enabled(); + + bool AntennaAvailable() const; + + Nullable GetFrequency() const; + + double FrequencyUpperBound() const; + + double FrequencyLowerBound() const; + + double ChannelWidth() const; + + already_AddRefed Enable(double aFrequency); + + already_AddRefed Disable(); + + already_AddRefed SetFrequency(double aFrequency); + + already_AddRefed SeekUp(); + + already_AddRefed SeekDown(); + + already_AddRefed CancelSeek(); + + IMPL_EVENT_HANDLER(enabled); + IMPL_EVENT_HANDLER(disabled); + IMPL_EVENT_HANDLER(antennaavailablechange); + IMPL_EVENT_HANDLER(frequencychange); + +private: + ~FMRadio(); + + hal::SwitchState mHeadphoneState; + bool mHasInternalAntenna; + bool mIsShutdown; +}; + +END_FMRADIO_NAMESPACE + +#endif // mozilla_dom_FMRadio_h + diff --git a/dom/fmradio/FMRadioCommon.h b/dom/fmradio/FMRadioCommon.h new file mode 100644 index 00000000000..42fc690adb7 --- /dev/null +++ b/dom/fmradio/FMRadioCommon.h @@ -0,0 +1,42 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef FMRADIOCOMMON_H_ +#define FMRADIOCOMMON_H_ + +#include "mozilla/Observer.h" + +#undef FM_LOG +#if defined(ANDROID) +#include +#define FM_LOG(FMRADIO_LOG_INFO, args...) \ + __android_log_print(ANDROID_LOG_INFO, \ + FMRADIO_LOG_INFO, \ + ## args) +#else +#define FM_LOG(args...) +#endif + +#define BEGIN_FMRADIO_NAMESPACE \ + namespace mozilla { namespace dom { +#define END_FMRADIO_NAMESPACE \ + } /* namespace dom */ } /* namespace mozilla */ + +BEGIN_FMRADIO_NAMESPACE + +enum FMRadioEventType +{ + FrequencyChanged, + EnabledChanged +}; + +typedef mozilla::Observer FMRadioEventObserver; +typedef mozilla::ObserverList FMRadioEventObserverList; + +END_FMRADIO_NAMESPACE + +#endif /* FMRADIOCOMMON_H_ */ + diff --git a/dom/fmradio/FMRadioService.cpp b/dom/fmradio/FMRadioService.cpp new file mode 100644 index 00000000000..b6bf2599b53 --- /dev/null +++ b/dom/fmradio/FMRadioService.cpp @@ -0,0 +1,800 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FMRadioService.h" +#include "mozilla/Hal.h" +#include "nsIAudioManager.h" +#include "AudioManager.h" +#include "nsDOMClassInfo.h" +#include "mozilla/Preferences.h" +#include "mozilla/dom/FMRadioChild.h" +#include "nsIObserverService.h" +#include "nsISettingsService.h" +#include "nsJSUtils.h" +#include "nsCxPusher.h" + +#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_RIL_RADIO_DISABLED "ril.radio.disabled" + +using namespace mozilla::hal; +using mozilla::Preferences; + +BEGIN_FMRADIO_NAMESPACE + +// static +IFMRadioService* +IFMRadioService::Singleton() +{ + if (XRE_GetProcessType() != GeckoProcessType_Default) { + return FMRadioChild::Singleton(); + } else { + return FMRadioService::Singleton(); + } +} + +StaticRefPtr FMRadioService::sFMRadioService; + +FMRadioService::FMRadioService() + : mPendingFrequencyInKHz(0) + , mState(Disabled) + , mHasReadRilSetting(false) + , mRilDisabled(false) + , mPendingRequest(nullptr) + , mObserverList(FMRadioEventObserverList()) +{ + + // Read power state and frequency from Hal. + mEnabled = IsFMRadioOn(); + if (mEnabled) { + mPendingFrequencyInKHz = GetFMRadioFrequency(); + SetState(Enabled); + } + + switch (Preferences::GetInt("dom.fmradio.band", BAND_87500_108000_kHz)) { + case BAND_76000_90000_kHz: + mUpperBoundInKHz = 90000; + mLowerBoundInKHz = 76000; + break; + case BAND_76000_108000_kHz: + mUpperBoundInKHz = 108000; + mLowerBoundInKHz = 76000; + break; + case BAND_87500_108000_kHz: + default: + mUpperBoundInKHz = 108000; + mLowerBoundInKHz = 87500; + break; + } + + switch (Preferences::GetInt("dom.fmradio.channelWidth", + CHANNEL_WIDTH_100KHZ)) { + case CHANNEL_WIDTH_200KHZ: + mChannelWidthInKHz = 200; + break; + case CHANNEL_WIDTH_50KHZ: + mChannelWidthInKHz = 50; + break; + case CHANNEL_WIDTH_100KHZ: + default: + mChannelWidthInKHz = 100; + break; + } + + nsCOMPtr obs = services::GetObserverService(); + + if (NS_FAILED(obs->AddObserver(this, + MOZSETTINGS_CHANGED_ID, + /* useWeak */ false))) { + NS_WARNING("Failed to add settings change observer!"); + } + + RegisterFMRadioObserver(this); +} + +FMRadioService::~FMRadioService() +{ + UnregisterFMRadioObserver(this); +} + +class EnableRunnable MOZ_FINAL : public nsRunnable +{ +public: + EnableRunnable(int32_t aUpperLimit, int32_t aLowerLimit, int32_t aSpaceType) + : mUpperLimit(aUpperLimit) + , mLowerLimit(aLowerLimit) + , mSpaceType(aSpaceType) { } + + NS_IMETHOD Run() + { + FMRadioSettings info; + info.upperLimit() = mUpperLimit; + info.lowerLimit() = mLowerLimit; + info.spaceType() = mSpaceType; + + EnableFMRadio(info); + + nsCOMPtr audioManager = + do_GetService(NS_AUDIOMANAGER_CONTRACTID); + audioManager->SetFmRadioAudioEnabled(true); + + // TODO apply path from bug 862899: AudioChannelAgent per process + return NS_OK; + } + +private: + int32_t mUpperLimit; + 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 +{ +public: + NS_DECL_ISUPPORTS + + ReadRilSettingTask(nsRefPtr aPendingRequest) + : mPendingRequest(aPendingRequest) { } + + NS_IMETHOD + Handle(const nsAString& aName, const JS::Value& aResult) + { + FMRadioService* fmRadioService = FMRadioService::Singleton(); + MOZ_ASSERT(mPendingRequest == fmRadioService->mPendingRequest); + + fmRadioService->mHasReadRilSetting = 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) { + 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( + NS_LITERAL_STRING("Airplane mode currently enabled")), Disabled); + } + + return NS_OK; + } + + NS_IMETHOD + HandleError(const nsAString& aName) + { + FMRadioService* fmRadioService = FMRadioService::Singleton(); + MOZ_ASSERT(mPendingRequest == fmRadioService->mPendingRequest); + + fmRadioService->TransitionState(ErrorResponse( + NS_LITERAL_STRING("Unexpected error")), Disabled); + + return NS_OK; + } + +private: + nsRefPtr mPendingRequest; +}; + +NS_IMPL_ISUPPORTS1(ReadRilSettingTask, nsISettingsServiceCallback) + +class DisableRunnable MOZ_FINAL : public nsRunnable +{ +public: + DisableRunnable() { } + + NS_IMETHOD Run() + { + // Fix Bug 796733. DisableFMRadio should be called before + // SetFmRadioAudioEnabled to prevent the annoying beep sound. + DisableFMRadio(); + + nsCOMPtr audioManager = + do_GetService(NS_AUDIOMANAGER_CONTRACTID); + + audioManager->SetFmRadioAudioEnabled(false); + + return NS_OK; + } +}; + +class SetFrequencyRunnable MOZ_FINAL : public nsRunnable +{ +public: + SetFrequencyRunnable(int32_t aFrequency) + : mFrequency(aFrequency) { } + + NS_IMETHOD Run() + { + SetFMRadioFrequency(mFrequency); + + FMRadioService* fmRadioService = FMRadioService::Singleton(); + fmRadioService->UpdateFrequency(); + + return NS_OK; + } + +private: + int32_t mFrequency; +}; + +class SeekRunnable MOZ_FINAL : public nsRunnable +{ +public: + SeekRunnable(FMRadioSeekDirection aDirection) : mDirection(aDirection) { } + + NS_IMETHOD Run() + { + switch (mDirection) { + case FM_RADIO_SEEK_DIRECTION_UP: + case FM_RADIO_SEEK_DIRECTION_DOWN: + FMRadioSeek(mDirection); + break; + default: + MOZ_CRASH(); + } + + return NS_OK; + } + +private: + FMRadioSeekDirection mDirection; +}; + +void +FMRadioService::TransitionState(const FMRadioResponseType& aResponse, + FMRadioState aState) +{ + if (mPendingRequest) { + mPendingRequest->SetReply(aResponse); + NS_DispatchToMainThread(mPendingRequest); + } + + SetState(aState); +} + +void +FMRadioService::SetState(FMRadioState aState) +{ + mState = aState; + mPendingRequest = nullptr; +} + +void +FMRadioService::AddObserver(FMRadioEventObserver* aObserver) +{ + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); + mObserverList.AddObserver(aObserver); +} + +void +FMRadioService::RemoveObserver(FMRadioEventObserver* aObserver) +{ + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); + mObserverList.RemoveObserver(aObserver); + + if (mObserverList.Length() == 0) + { + // Turning off the FM radio HW because observer list is empty. + if (IsFMRadioOn()) { + NS_DispatchToMainThread(new DisableRunnable()); + } + } +} + +/** + * Round the frequency to match the range of frequency and the channel width. If + * the given frequency is out of range, return 0. For example: + * - lower: 87500KHz, upper: 108000KHz, channel width: 200KHz + * 87.6MHz is rounded to 87700KHz + * 87.58MHz is rounded to 87500KHz + * 87.49MHz is rounded to 87500KHz + * 109MHz is not rounded, 0 will be returned + * + * We take frequency in MHz to prevent precision losing, and return rounded + * value in KHz for Gonk using. + */ +int32_t +FMRadioService::RoundFrequency(double aFrequencyInMHz) +{ + double halfChannelWidthInMHz = mChannelWidthInKHz / 1000.0 / 2; + + // Make sure 87.49999MHz would be rounded to the lower bound when + // the lower bound is 87500KHz. + if (aFrequencyInMHz < mLowerBoundInKHz / 1000.0 - halfChannelWidthInMHz || + aFrequencyInMHz > mUpperBoundInKHz / 1000.0 + halfChannelWidthInMHz) { + return 0; + } + + int32_t partToBeRounded = round(aFrequencyInMHz * 1000) - mLowerBoundInKHz; + int32_t roundedPart = round(partToBeRounded / (double)mChannelWidthInKHz) * + mChannelWidthInKHz; + + return mLowerBoundInKHz + roundedPart; +} + +bool +FMRadioService::IsEnabled() const +{ + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); + return IsFMRadioOn(); +} + +double +FMRadioService::GetFrequency() const +{ + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); + if (IsEnabled()) { + int32_t frequencyInKHz = GetFMRadioFrequency(); + return frequencyInKHz / 1000.0; + } + + return 0; +} + +double +FMRadioService::GetFrequencyUpperBound() const +{ + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); + return mUpperBoundInKHz / 1000.0; +} + +double +FMRadioService::GetFrequencyLowerBound() const +{ + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); + return mLowerBoundInKHz / 1000.0; +} + +double +FMRadioService::GetChannelWidth() const +{ + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); + return mChannelWidthInKHz / 1000.0; +} + +void +FMRadioService::Enable(double aFrequencyInMHz, ReplyRunnable* aReplyRunnable) +{ + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); + MOZ_ASSERT(aReplyRunnable); + + switch (mState) { + case Seeking: + case Enabled: + aReplyRunnable->SetReply( + ErrorResponse(NS_LITERAL_STRING("FM radio currently enabled"))); + NS_DispatchToMainThread(aReplyRunnable); + return; + case Disabling: + aReplyRunnable->SetReply( + ErrorResponse(NS_LITERAL_STRING("FM radio currently disabling"))); + NS_DispatchToMainThread(aReplyRunnable); + return; + case Enabling: + aReplyRunnable->SetReply( + ErrorResponse(NS_LITERAL_STRING("FM radio currently enabling"))); + NS_DispatchToMainThread(aReplyRunnable); + return; + case Disabled: + break; + } + + int32_t roundedFrequency = RoundFrequency(aFrequencyInMHz); + + if (!roundedFrequency) { + aReplyRunnable->SetReply(ErrorResponse( + NS_LITERAL_STRING("Frequency is out of range"))); + NS_DispatchToMainThread(aReplyRunnable); + return; + } + + if (mHasReadRilSetting && mRilDisabled) { + 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) { + nsCOMPtr settings = + do_GetService("@mozilla.org/settingsService;1"); + + nsCOMPtr settingsLock; + nsresult rv = settings->CreateLock(getter_AddRefs(settingsLock)); + if (NS_FAILED(rv)) { + TransitionState(ErrorResponse( + NS_LITERAL_STRING("Can't create settings lock")), Disabled); + return; + } + + nsRefPtr callback = + new ReadRilSettingTask(mPendingRequest); + + rv = settingsLock->Get(SETTING_KEY_RIL_RADIO_DISABLED, 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(ReplyRunnable* aReplyRunnable) +{ + // When airplane-mode is enabled, we will call this function from + // FMRadioService::Observe without passing a ReplyRunnable, so we have to + // check if |aReplyRunnable| is null before we dispatch it. + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); + + switch (mState) { + case Disabling: + if (aReplyRunnable) { + aReplyRunnable->SetReply( + ErrorResponse(NS_LITERAL_STRING("FM radio currently disabling"))); + NS_DispatchToMainThread(aReplyRunnable); + } + return; + case Disabled: + if (aReplyRunnable) { + aReplyRunnable->SetReply( + ErrorResponse(NS_LITERAL_STRING("FM radio currently disabled"))); + NS_DispatchToMainThread(aReplyRunnable); + } + return; + case Enabled: + case Enabling: + case Seeking: + break; + } + + nsRefPtr enablingRequest = mPendingRequest; + + // If the FM Radio is currently seeking, no fail-to-seek or similar + // event will be fired, execute the seek callback manually. + if (mState == Seeking) { + TransitionState(ErrorResponse( + NS_LITERAL_STRING("Seek action is cancelled")), Disabling); + } + + FMRadioState preState = mState; + SetState(Disabling); + mPendingRequest = aReplyRunnable; + + 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) { + SetState(Disabled); + + if (aReplyRunnable) { + aReplyRunnable->SetReply(SuccessResponse()); + NS_DispatchToMainThread(aReplyRunnable); + } + } + + return; + } + + DoDisable(); +} + +void +FMRadioService::DoDisable() +{ + // To make such codes work: + // navigator.mozFMRadio.disable(); + // navigator.mozFMRadio.ondisabled = function() { + // console.log("We will catch disabled event "); + // }; + // we need to call hal::DisableFMRadio() asynchronously. Same reason for + // EnableRunnable and SetFrequencyRunnable. + NS_DispatchToMainThread(new DisableRunnable()); +} + +void +FMRadioService::SetFrequency(double aFrequencyInMHz, + ReplyRunnable* aReplyRunnable) +{ + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); + MOZ_ASSERT(aReplyRunnable); + + switch (mState) { + case Disabled: + aReplyRunnable->SetReply( + ErrorResponse(NS_LITERAL_STRING("FM radio currently disabled"))); + NS_DispatchToMainThread(aReplyRunnable); + return; + case Enabling: + aReplyRunnable->SetReply( + ErrorResponse(NS_LITERAL_STRING("FM radio currently enabling"))); + NS_DispatchToMainThread(aReplyRunnable); + return; + case Disabling: + aReplyRunnable->SetReply( + ErrorResponse(NS_LITERAL_STRING("FM radio currently disabling"))); + NS_DispatchToMainThread(aReplyRunnable); + return; + case Seeking: + CancelFMRadioSeek(); + TransitionState(ErrorResponse( + NS_LITERAL_STRING("Seek action is cancelled")), Enabled); + break; + case Enabled: + break; + } + + int32_t roundedFrequency = RoundFrequency(aFrequencyInMHz); + + if (!roundedFrequency) { + aReplyRunnable->SetReply(ErrorResponse( + NS_LITERAL_STRING("Frequency is out of range"))); + NS_DispatchToMainThread(aReplyRunnable); + return; + } + + NS_DispatchToMainThread(new SetFrequencyRunnable(roundedFrequency)); + + aReplyRunnable->SetReply(SuccessResponse()); + NS_DispatchToMainThread(aReplyRunnable); +} + +void +FMRadioService::Seek(FMRadioSeekDirection aDirection, + ReplyRunnable* aReplyRunnable) +{ + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); + MOZ_ASSERT(aReplyRunnable); + + switch (mState) { + case Enabling: + aReplyRunnable->SetReply( + ErrorResponse(NS_LITERAL_STRING("FM radio currently enabling"))); + NS_DispatchToMainThread(aReplyRunnable); + return; + case Disabled: + aReplyRunnable->SetReply( + ErrorResponse(NS_LITERAL_STRING("FM radio currently disabled"))); + NS_DispatchToMainThread(aReplyRunnable); + return; + case Seeking: + aReplyRunnable->SetReply( + ErrorResponse(NS_LITERAL_STRING("FM radio currently seeking"))); + NS_DispatchToMainThread(aReplyRunnable); + return; + case Disabling: + aReplyRunnable->SetReply( + ErrorResponse(NS_LITERAL_STRING("FM radio currently disabling"))); + NS_DispatchToMainThread(aReplyRunnable); + return; + case Enabled: + break; + } + + SetState(Seeking); + mPendingRequest = aReplyRunnable; + + NS_DispatchToMainThread(new SeekRunnable(aDirection)); +} + +void +FMRadioService::CancelSeek(ReplyRunnable* aReplyRunnable) +{ + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); + MOZ_ASSERT(aReplyRunnable); + + // We accept canceling seek request only if it's currently seeking. + if (mState != Seeking) { + aReplyRunnable->SetReply( + ErrorResponse(NS_LITERAL_STRING("FM radio currently not seeking"))); + NS_DispatchToMainThread(aReplyRunnable); + return; + } + + // Cancel the seek immediately to prevent it from completing. + CancelFMRadioSeek(); + + 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 PRUnichar * 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":"ril.radio.disabled","value":true} + AutoSafeJSContext cx; + const nsDependentString dataStr(aData); + JS::Rooted val(cx); + if (!JS_ParseJSON(cx, dataStr.get(), dataStr.Length(), &val) || + !val.isObject()) { + return NS_OK; + } + + JSObject& obj(val.toObject()); + JS::Rooted key(cx); + if (!JS_GetProperty(cx, &obj, "key", &key) || + !key.isString()) { + return NS_OK; + } + + JS::Rooted jsKey(cx, key.toString()); + nsDependentJSString keyStr; + if (!keyStr.init(cx, jsKey)) { + return NS_OK; + } + + JS::Rooted value(cx); + if (!JS_GetProperty(cx, &obj, "value", &value)) { + return NS_OK; + } + + if (keyStr.EqualsLiteral(SETTING_KEY_RIL_RADIO_DISABLED)) { + if (!value.isBoolean()) { + return NS_OK; + } + + mRilDisabled = value.toBoolean(); + mHasReadRilSetting = true; + + // Disable the FM radio HW if Airplane mode is enabled. + if (mRilDisabled) { + Disable(nullptr); + } + + return NS_OK; + } + + return NS_OK; +} + +void +FMRadioService::NotifyFMRadioEvent(FMRadioEventType aType) +{ + mObserverList.Broadcast(aType); +} + +void +FMRadioService::Notify(const FMRadioOperationInformation& aInfo) +{ + switch (aInfo.operation()) { + case FM_RADIO_OPERATION_ENABLE: + MOZ_ASSERT(IsFMRadioOn()); + MOZ_ASSERT(mState == Disabling || mState == Enabling); + + // If we're disabling, disable the radio right now. + if (mState == Disabling) { + DoDisable(); + return; + } + + // Fire success callback on the enable request. + TransitionState(SuccessResponse(), Enabled); + + // To make sure the FM app will get the right frequency after the FM + // radio is enabled, we have to set the frequency first. + SetFMRadioFrequency(mPendingFrequencyInKHz); + + // Update the current frequency without sending the`FrequencyChanged` + // event, to make sure the FM app will get the right frequency when the + // `EnabledChange` event is sent. + mPendingFrequencyInKHz = GetFMRadioFrequency(); + UpdatePowerState(); + + // The frequency was changed from '0' to some meaningful number, so we + // should send the `FrequencyChanged` event manually. + NotifyFMRadioEvent(FrequencyChanged); + break; + case FM_RADIO_OPERATION_DISABLE: + MOZ_ASSERT(mState == Disabling); + + TransitionState(SuccessResponse(), Disabled); + UpdatePowerState(); + break; + case FM_RADIO_OPERATION_SEEK: + + // Seek action might be cancelled by SetFrequency(), we need to check if + // the current state is Seeking. + if (mState == Seeking) { + TransitionState(SuccessResponse(), Enabled); + } + + UpdateFrequency(); + break; + default: + MOZ_CRASH(); + } +} + +void +FMRadioService::UpdatePowerState() +{ + bool enabled = IsFMRadioOn(); + if (enabled != mEnabled) { + mEnabled = enabled; + NotifyFMRadioEvent(EnabledChanged); + } +} + +void +FMRadioService::UpdateFrequency() +{ + int32_t frequency = GetFMRadioFrequency(); + if (mPendingFrequencyInKHz != frequency) { + mPendingFrequencyInKHz = frequency; + NotifyFMRadioEvent(FrequencyChanged); + } +} + +// static +FMRadioService* +FMRadioService::Singleton() +{ + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); + MOZ_ASSERT(NS_IsMainThread()); + + if (!sFMRadioService) { + sFMRadioService = new FMRadioService(); + } + + return sFMRadioService; +} + +NS_IMPL_ISUPPORTS1(FMRadioService, nsIObserver) + +END_FMRADIO_NAMESPACE + diff --git a/dom/fmradio/FMRadioService.h b/dom/fmradio/FMRadioService.h new file mode 100644 index 00000000000..f9b2d09fed8 --- /dev/null +++ b/dom/fmradio/FMRadioService.h @@ -0,0 +1,203 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_fmradioservice_h__ +#define mozilla_dom_fmradioservice_h__ + +#include "mozilla/dom/PFMRadioRequest.h" +#include "FMRadioCommon.h" +#include "mozilla/Hal.h" +#include "mozilla/StaticPtr.h" + +BEGIN_FMRADIO_NAMESPACE + +class ReplyRunnable : public nsRunnable +{ +public: + ReplyRunnable() : mResponseType(SuccessResponse()) {} + virtual ~ReplyRunnable() {} + + void + SetReply(const FMRadioResponseType& aResponseType) + { + mResponseType = aResponseType; + } + +protected: + FMRadioResponseType mResponseType; +}; + +/** + * The FMRadio Service Interface for FMRadio. + * + * There are two concrete classes which implement this interface: + * - FMRadioService + * It's used in the main process, implements all the logics about FM Radio. + * + * - FMRadioChild + * It's used in subprocess. It's a kind of proxy which just sends all + * the requests to main process through IPC channel. + * + * All the requests coming from the content page will be redirected to the + * concrete class object. + * + * Consider navigator.mozFMRadio.enable(). Here is the call sequence: + * - OOP + * Child: + * (1) Call navigator.mozFMRadio.enable(). + * (2) Return a DOMRequest object, and call FMRadioChild.Enable() with a + * ReplyRunnable object. + * (3) Send IPC message to main process. + * Parent: + * (4) Call FMRadioService::Enable() with a ReplyRunnable object. + * (5) Call hal::EnableFMRadio(). + * (6) Notify FMRadioService object when FM radio HW is enabled. + * (7) Dispatch the ReplyRunnable object created in (4). + * (8) Send IPC message back to child process. + * Child: + * (9) Dispatch the ReplyRunnable object created in (2). + * (10) Fire success callback of the DOMRequest Object created in (2). + * _ _ _ _ _ _ _ _ _ _ _ _ _ _ + * | OOP | + * | | + * Page FMRadio | FMRadioChild IPC | FMRadioService Hal + * | (1) | | | | | | | + * |----->| (2) | | | | | | + * | |--------|--------->| (3) | | | | + * | | | |-----------> | | (4) | | + * | | | | |--|---------->| (5) | + * | | | | | | |--------->| + * | | | | | | | (6) | + * | | | | | | (7) |<---------| + * | | | | (8) |<-|-----------| | + * | | (9) | |<----------- | | | | + * | (10) |<-------|----------| | | | | + * |<-----| | | | | | | + * | | + * |_ _ _ _ _ _ _ _ _ _ _ _ _ _| + * - non-OOP + * In non-OOP model, we don't need to send messages between processes, so + * the call sequences are much more simpler, it almost just follows the + * sequences presented in OOP model: (1) (2) (5) (6) (9) and (10). + * + */ +class IFMRadioService +{ +protected: + virtual ~IFMRadioService() { } + +public: + virtual bool IsEnabled() const = 0; + virtual double GetFrequency() const = 0; + virtual double GetFrequencyUpperBound() const = 0; + virtual double GetFrequencyLowerBound() const = 0; + virtual double GetChannelWidth() const = 0; + + virtual void Enable(double aFrequency, ReplyRunnable* aReplyRunnable) = 0; + virtual void Disable(ReplyRunnable* aReplyRunnable) = 0; + virtual void SetFrequency(double aFrequency, ReplyRunnable* aReplyRunnable) = 0; + virtual void Seek(mozilla::hal::FMRadioSeekDirection aDirection, + ReplyRunnable* aReplyRunnable) = 0; + virtual void CancelSeek(ReplyRunnable* aReplyRunnable) = 0; + + /** + * Register handler to receive the FM Radio events, including: + * - StateChangedEvent + * - FrequencyChangedEvent + * + * Called by FMRadio and FMRadioParent. + */ + virtual void AddObserver(FMRadioEventObserver* aObserver) = 0; + virtual void RemoveObserver(FMRadioEventObserver* aObserver) = 0; + + /** + * Static method to return the singleton instance. If it's in the child + * process, we will get an object of FMRadioChild. + */ + static IFMRadioService* Singleton(); +}; + +enum FMRadioState +{ + Disabled, + Disabling, + Enabling, + Enabled, + Seeking +}; + +class FMRadioService MOZ_FINAL : public IFMRadioService + , public hal::FMRadioObserver + , public nsIObserver +{ + friend class ReadRilSettingTask; + friend class SetFrequencyRunnable; + +public: + static FMRadioService* Singleton(); + virtual ~FMRadioService(); + + NS_DECL_ISUPPORTS + + virtual bool IsEnabled() const MOZ_OVERRIDE; + virtual double GetFrequency() const MOZ_OVERRIDE; + virtual double GetFrequencyUpperBound() const MOZ_OVERRIDE; + virtual double GetFrequencyLowerBound() const MOZ_OVERRIDE; + virtual double GetChannelWidth() const MOZ_OVERRIDE; + + virtual void Enable(double aFrequency, ReplyRunnable* aReplyRunnable) MOZ_OVERRIDE; + virtual void Disable(ReplyRunnable* aReplyRunnable) MOZ_OVERRIDE; + virtual void SetFrequency(double aFrequency, ReplyRunnable* aReplyRunnable) MOZ_OVERRIDE; + virtual void Seek(mozilla::hal::FMRadioSeekDirection aDirection, + ReplyRunnable* aReplyRunnable) MOZ_OVERRIDE; + virtual void CancelSeek(ReplyRunnable* aReplyRunnable) MOZ_OVERRIDE; + + virtual void AddObserver(FMRadioEventObserver* aObserver) MOZ_OVERRIDE; + virtual void RemoveObserver(FMRadioEventObserver* aObserver) 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(); + void TransitionState(const FMRadioResponseType& aResponse, FMRadioState aState); + void SetState(FMRadioState aState); + void UpdatePowerState(); + void UpdateFrequency(); + +private: + bool mEnabled; + + int32_t mPendingFrequencyInKHz; + + FMRadioState mState; + + bool mHasReadRilSetting; + bool mRilDisabled; + + double mUpperBoundInKHz; + double mLowerBoundInKHz; + double mChannelWidthInKHz; + + nsRefPtr mPendingRequest; + + FMRadioEventObserverList mObserverList; + + static StaticRefPtr sFMRadioService; +}; + +END_FMRADIO_NAMESPACE + +#endif // mozilla_dom_fmradioservice_h__ + diff --git a/dom/fm/Makefile.in b/dom/fmradio/Makefile.in similarity index 88% rename from dom/fm/Makefile.in rename to dom/fmradio/Makefile.in index f49a4f67288..7ef3b05f80c 100644 --- a/dom/fm/Makefile.in +++ b/dom/fmradio/Makefile.in @@ -2,14 +2,15 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this file, # You can obtain one at http://mozilla.org/MPL/2.0/. -DEPTH = @DEPTH@ +DEPTH = ../.. topsrcdir = @top_srcdir@ srcdir = @srcdir@ VPATH = @srcdir@ include $(DEPTH)/config/autoconf.mk -LIBRARY_NAME = domfm_s +LIBRARY_NAME = domfmradio_s + include $(topsrcdir)/dom/dom-config.mk include $(topsrcdir)/config/rules.mk diff --git a/dom/fmradio/moz.build b/dom/fmradio/moz.build new file mode 100644 index 00000000000..fc2f17f0f1e --- /dev/null +++ b/dom/fmradio/moz.build @@ -0,0 +1,33 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +if CONFIG['MOZ_B2G_FM']: + DIRS += [ + 'ipc', + ] + + MODULE = 'dom' + + EXPORTS.mozilla.dom += [ + 'FMRadio.h', + 'FMRadioCommon.h', + 'FMRadioService.h', + ] + + CPP_SOURCES += [ + 'FMRadio.cpp', + 'FMRadioService.cpp', + ] + + LIBXUL_LIBRARY = True + +IPDL_SOURCES += [ + 'ipc/PFMRadio.ipdl', + 'ipc/PFMRadioRequest.ipdl', +] + +FAIL_ON_WARNINGS = True + diff --git a/dom/moz.build b/dom/moz.build index cfe9ab268af..5aeb1ba6a7a 100644 --- a/dom/moz.build +++ b/dom/moz.build @@ -49,6 +49,7 @@ PARALLEL_DIRS += [ 'devicestorage', 'encoding', 'file', + 'fmradio', 'media', 'messages', 'power', @@ -86,9 +87,6 @@ if CONFIG['MOZ_B2G_RIL']: 'voicemail', ] -if CONFIG['MOZ_B2G_FM']: - PARALLEL_DIRS += ['fm'] - if CONFIG['MOZ_PAY']: PARALLEL_DIRS += ['payment'] diff --git a/dom/permission/tests/test_fmradio.html b/dom/permission/tests/test_fmradio.html index 5c2ca397f3d..e85ce6ea73b 100644 --- a/dom/permission/tests/test_fmradio.html +++ b/dom/permission/tests/test_fmradio.html @@ -19,8 +19,9 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=815105 var gData = [ { perm: ["fmradio"], + needParentPerm: true, obj: "mozFMRadio", - idl: "nsIDOMFMRadio", + webidl: "FMRadio", }, ] diff --git a/dom/webidl/FMRadio.webidl b/dom/webidl/FMRadio.webidl new file mode 100644 index 00000000000..8184a4d4c8a --- /dev/null +++ b/dom/webidl/FMRadio.webidl @@ -0,0 +1,102 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +interface FMRadio : EventTarget { + /* Indicates if the FM radio is enabled. */ + readonly attribute boolean enabled; + + /* Indicates if the antenna is plugged and available. */ + readonly attribute boolean antennaAvailable; + + /** + * Current frequency in MHz. The value will be null if the FM radio is + * disabled. + */ + readonly attribute double? frequency; + + /* The upper bound of frequency in MHz. */ + readonly attribute double frequencyUpperBound; + + /* The lower bound of frequency in MHz. */ + readonly attribute double frequencyLowerBound; + + /** + * The difference in frequency between two "adjacent" channels, in MHz. That + * is, any two radio channels' frequencies differ by at least channelWidth + * MHz. Usually, the value is one of: + * - 0.05 MHz + * - 0.1 MHz + * - 0.2 MHz + */ + readonly attribute double channelWidth; + + /* Fired when the FM radio is enabled. */ + [SetterThrows] + attribute EventHandler onenabled; + + /* Fired when the FM radio is disabled. */ + [SetterThrows] + attribute EventHandler ondisabled; + + /** + * Fired when the antenna becomes available or unavailable, i.e., fired when + * the antennaAvailable attribute changes. + */ + [SetterThrows] + attribute EventHandler onantennaavailablechange; + + /* Fired when the FM radio's frequency is changed. */ + [SetterThrows] + attribute EventHandler onfrequencychange; + + /** + * Power the FM radio off. The disabled event will be fired if this request + * completes successfully. + */ + DOMRequest disable(); + + /** + * Power the FM radio on, and tune the radio to the given frequency in MHz. + * This will fail if the given frequency is out of range. The enabled event + * and frequencychange event will be fired if this request completes + * successfully. + */ + DOMRequest enable(double frequency); + + /** + * Tune the FM radio to the given frequency. This will fail if the given + * frequency is out of range. + * + * Note that the FM radio may not tuned to the exact frequency given. To get + * the frequency the radio is actually tuned to, wait for the request to fire + * sucess (or wait for the frequencychange event to fire), and then read the + * frequency attribute. + */ + DOMRequest setFrequency(double frequency); + + /** + * Tell the FM radio to seek up to the next channel. If the frequency is + * successfully changed, the frequencychange event will be triggered. + * + * Only one seek is allowed at once: If the radio is seeking when the seekUp + * is called, error will be fired. + */ + DOMRequest seekUp(); + + /** + * Tell the FM radio to seek down to the next channel. If the frequency is + * successfully changed, the frequencychange event will be triggered. + * + * Only one seek is allowed at once: If the radio is seeking when the + * seekDown is called, error will be fired. + */ + DOMRequest seekDown(); + + /** + * Cancel the seek action. If the radio is not currently seeking up or down, + * error will be fired. + */ + DOMRequest cancelSeek(); +}; + diff --git a/dom/webidl/Navigator.webidl b/dom/webidl/Navigator.webidl index ce19de4e847..bffa9d8ed49 100644 --- a/dom/webidl/Navigator.webidl +++ b/dom/webidl/Navigator.webidl @@ -299,6 +299,13 @@ partial interface Navigator { }; #endif // MOZ_B2G_BT +#ifdef MOZ_B2G_FM +partial interface Navigator { + [Throws, Func="Navigator::HasFMRadioSupport"] + readonly attribute FMRadio mozFMRadio; +}; +#endif // MOZ_B2G_FM + #ifdef MOZ_TIME_MANAGER // nsIDOMMozNavigatorTime partial interface Navigator { diff --git a/dom/webidl/WebIDL.mk b/dom/webidl/WebIDL.mk index 8c29c81ee80..4564c62fa8c 100644 --- a/dom/webidl/WebIDL.mk +++ b/dom/webidl/WebIDL.mk @@ -523,6 +523,10 @@ webidl_files += \ $(NULL) endif +ifdef MOZ_B2G_FM +webidl_files += FMRadio.webidl +endif + ifdef ENABLE_TESTS test_webidl_files := \ TestCodeGen.webidl \ diff --git a/layout/build/Makefile.in b/layout/build/Makefile.in index 406627e5044..d813c66ab8b 100644 --- a/layout/build/Makefile.in +++ b/layout/build/Makefile.in @@ -123,7 +123,8 @@ endif #} ifdef MOZ_B2G_FM #{ SHARED_LIBRARY_LIBS += \ - $(DEPTH)/dom/fm/$(LIB_PREFIX)domfm_s.$(LIB_SUFFIX) \ + $(DEPTH)/dom/fmradio/$(LIB_PREFIX)domfmradio_s.$(LIB_SUFFIX) \ + $(DEPTH)/dom/fmradio/ipc/$(LIB_PREFIX)domfmradio_s.$(LIB_SUFFIX) \ $(NULL) endif #} @@ -327,7 +328,7 @@ LOCAL_INCLUDES += -I$(topsrcdir)/dom/system/gonk endif #} ifdef MOZ_B2G_FM #{ -LOCAL_INCLUDES += -I$(topsrcdir)/dom/fm +LOCAL_INCLUDES += -I$(topsrcdir)/dom/fmradio endif #} ifdef MOZ_B2G_BT #{ diff --git a/layout/build/nsLayoutModule.cpp b/layout/build/nsLayoutModule.cpp index dbd4cad2a83..f28ea2d3d18 100644 --- a/layout/build/nsLayoutModule.cpp +++ b/layout/build/nsLayoutModule.cpp @@ -125,11 +125,6 @@ using mozilla::dom::gonk::AudioManager; using mozilla::system::nsVolumeService; #endif -#ifdef MOZ_B2G_FM -#include "FMRadio.h" -using mozilla::dom::fm::FMRadio; -#endif - #include "AudioChannelAgent.h" using mozilla::dom::AudioChannelAgent; @@ -303,10 +298,6 @@ NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsSynthVoiceRegistry, NS_GENERIC_FACTORY_CONSTRUCTOR(AudioManager) #endif -#ifdef MOZ_B2G_FM -NS_GENERIC_FACTORY_CONSTRUCTOR(FMRadio) -#endif - NS_GENERIC_FACTORY_CONSTRUCTOR(AudioChannelAgent) NS_GENERIC_FACTORY_CONSTRUCTOR(nsDeviceSensors) @@ -761,10 +752,6 @@ NS_DEFINE_NAMED_CID(NS_AUDIOMANAGER_CID); NS_DEFINE_NAMED_CID(NS_VOLUMESERVICE_CID); #endif -#ifdef MOZ_B2G_FM -NS_DEFINE_NAMED_CID(NS_FMRADIO_CID); -#endif - NS_DEFINE_NAMED_CID(NS_AUDIOCHANNELAGENT_CID); NS_DEFINE_NAMED_CID(NS_HTMLEDITOR_CID); @@ -1049,9 +1036,6 @@ static const mozilla::Module::CIDEntry kLayoutCIDs[] = { #ifdef MOZ_WIDGET_GONK { &kNS_AUDIOMANAGER_CID, true, NULL, AudioManagerConstructor }, { &kNS_VOLUMESERVICE_CID, true, NULL, nsVolumeServiceConstructor }, -#endif -#ifdef MOZ_B2G_FM - { &kNS_FMRADIO_CID, true, NULL, FMRadioConstructor }, #endif { &kNS_AUDIOCHANNELAGENT_CID, true, NULL, AudioChannelAgentConstructor }, { &kNS_HTMLEDITOR_CID, false, NULL, nsHTMLEditorConstructor }, @@ -1207,9 +1191,6 @@ static const mozilla::Module::ContractIDEntry kLayoutContracts[] = { #ifdef MOZ_WIDGET_GONK { NS_AUDIOMANAGER_CONTRACTID, &kNS_AUDIOMANAGER_CID }, { NS_VOLUMESERVICE_CONTRACTID, &kNS_VOLUMESERVICE_CID }, -#endif -#ifdef MOZ_B2G_FM - { NS_FMRADIO_CONTRACTID, &kNS_FMRADIO_CID }, #endif { NS_AUDIOCHANNELAGENT_CONTRACTID, &kNS_AUDIOCHANNELAGENT_CID }, { "@mozilla.org/editor/htmleditor;1", &kNS_HTMLEDITOR_CID },