Bug 870676: Part 1 - Implement mozFMRadio in WebIDL. r=khuey,baku

This commit is contained in:
Pin Zhang 2013-08-28 10:24:23 -07:00
parent 68c202cebc
commit 39055cc1a0
32 changed files with 1652 additions and 1621 deletions

View File

@ -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');

View File

@ -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

View File

@ -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")

View File

@ -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<nsPIDOMWindow> win = GetWindowFromGlobal(aGlobal);
return win && CheckPermission(win, "fmradio");
}
#endif // MOZ_B2G_FM
#ifdef MOZ_TIME_MANAGER
/* static */
bool

View File

@ -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<nsRefPtr<Gamepad> >& 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<Geolocation> mGeolocation;
nsRefPtr<DesktopNotificationCenter> mNotification;
nsRefPtr<battery::BatteryManager> mBatteryManager;
#ifdef MOZ_B2G_FM
nsRefPtr<FMRadio> mFMRadio;
#endif
nsRefPtr<power::PowerManager> mPowerManager;
nsRefPtr<MobileMessageManager> mMobileMessageManager;
#ifdef MOZ_B2G_RIL

View File

@ -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)

View File

@ -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

View File

@ -48,7 +48,7 @@ endif
ifdef MOZ_B2G_FM
DOM_SRCDIRS += \
dom/fm \
dom/fmradio \
$(NULL)
endif

View File

@ -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

View File

@ -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]);

View File

@ -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();

View File

@ -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 <android/log.h>
#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<nsIAudioManager> 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<nsIAudioManager> 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<nsIFMRadioSettings> 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<nsIAudioManager> 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;
}

View File

@ -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<nsIAudioChannelAgent> mAudioChannelAgent;
hal::SwitchState mHeadphoneState;
bool mHasInternalAntenna;
bool mHidden;
};
} // namespace fm
} // namespace dom
} // namespace mozilla
#endif

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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);
};

View File

@ -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;
};

295
dom/fmradio/FMRadio.cpp Normal file
View File

@ -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<nsIDOMEventTarget*>(aFMRadio));
}
~FMRadioRequest() { }
NS_IMETHODIMP
Run()
{
MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
nsCOMPtr<nsIDOMEventTarget> target = do_QueryReferent(mFMRadio);
if (!target) {
return NS_OK;
}
FMRadio* fmRadio = static_cast<FMRadio*>(
static_cast<nsIDOMEventTarget*>(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<JSObject*> 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<double>
FMRadio::GetFrequency() const
{
return Enabled() ?
Nullable<double>(IFMRadioService::Singleton()->GetFrequency()) :
Nullable<double>();
}
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<DOMRequest>
FMRadio::Enable(double aFrequency)
{
nsCOMPtr<nsPIDOMWindow> win = GetOwner();
if (!win) {
return nullptr;
}
nsRefPtr<FMRadioRequest> r = new FMRadioRequest(win, this);
IFMRadioService::Singleton()->Enable(aFrequency, r);
return r.forget();
}
already_AddRefed<DOMRequest>
FMRadio::Disable()
{
nsCOMPtr<nsPIDOMWindow> win = GetOwner();
if (!win) {
return nullptr;
}
nsRefPtr<FMRadioRequest> r = new FMRadioRequest(win, this);
IFMRadioService::Singleton()->Disable(r);
return r.forget();
}
already_AddRefed<DOMRequest>
FMRadio::SetFrequency(double aFrequency)
{
nsCOMPtr<nsPIDOMWindow> win = GetOwner();
if (!win) {
return nullptr;
}
nsRefPtr<FMRadioRequest> r = new FMRadioRequest(win, this);
IFMRadioService::Singleton()->SetFrequency(aFrequency, r);
return r.forget();
}
already_AddRefed<DOMRequest>
FMRadio::SeekUp()
{
nsCOMPtr<nsPIDOMWindow> win = GetOwner();
if (!win) {
return nullptr;
}
nsRefPtr<FMRadioRequest> r = new FMRadioRequest(win, this);
IFMRadioService::Singleton()->Seek(FM_RADIO_SEEK_DIRECTION_UP, r);
return r.forget();
}
already_AddRefed<DOMRequest>
FMRadio::SeekDown()
{
nsCOMPtr<nsPIDOMWindow> win = GetOwner();
if (!win) {
return nullptr;
}
nsRefPtr<FMRadioRequest> r = new FMRadioRequest(win, this);
IFMRadioService::Singleton()->Seek(FM_RADIO_SEEK_DIRECTION_DOWN, r);
return r.forget();
}
already_AddRefed<DOMRequest>
FMRadio::CancelSeek()
{
nsCOMPtr<nsPIDOMWindow> win = GetOwner();
if (!win) {
return nullptr;
}
nsRefPtr<FMRadioRequest> 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

92
dom/fmradio/FMRadio.h Normal file
View File

@ -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<JSObject*> aScope) MOZ_OVERRIDE;
static bool Enabled();
bool AntennaAvailable() const;
Nullable<double> GetFrequency() const;
double FrequencyUpperBound() const;
double FrequencyLowerBound() const;
double ChannelWidth() const;
already_AddRefed<DOMRequest> Enable(double aFrequency);
already_AddRefed<DOMRequest> Disable();
already_AddRefed<DOMRequest> SetFrequency(double aFrequency);
already_AddRefed<DOMRequest> SeekUp();
already_AddRefed<DOMRequest> SeekDown();
already_AddRefed<DOMRequest> 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

View File

@ -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 <android/log.h>
#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<FMRadioEventType> FMRadioEventObserver;
typedef mozilla::ObserverList<FMRadioEventType> FMRadioEventObserverList;
END_FMRADIO_NAMESPACE
#endif /* FMRADIOCOMMON_H_ */

View File

@ -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> 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<nsIObserverService> 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<nsIAudioManager> 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<ReplyRunnable> 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<ReplyRunnable> 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<nsIAudioManager> 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<nsISettingsService> settings =
do_GetService("@mozilla.org/settingsService;1");
nsCOMPtr<nsISettingsServiceLock> 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<ReadRilSettingTask> 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<ReplyRunnable> 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<JS::Value> val(cx);
if (!JS_ParseJSON(cx, dataStr.get(), dataStr.Length(), &val) ||
!val.isObject()) {
return NS_OK;
}
JSObject& obj(val.toObject());
JS::Rooted<JS::Value> key(cx);
if (!JS_GetProperty(cx, &obj, "key", &key) ||
!key.isString()) {
return NS_OK;
}
JS::Rooted<JSString*> jsKey(cx, key.toString());
nsDependentJSString keyStr;
if (!keyStr.init(cx, jsKey)) {
return NS_OK;
}
JS::Rooted<JS::Value> 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

View File

@ -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<ReplyRunnable> mPendingRequest;
FMRadioEventObserverList mObserverList;
static StaticRefPtr<FMRadioService> sFMRadioService;
};
END_FMRADIO_NAMESPACE
#endif // mozilla_dom_fmradioservice_h__

View File

@ -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

33
dom/fmradio/moz.build Normal file
View File

@ -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

View File

@ -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']

View File

@ -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",
},
]
</script>

102
dom/webidl/FMRadio.webidl Normal file
View File

@ -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();
};

View File

@ -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 {

View File

@ -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 \

View File

@ -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 #{

View File

@ -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 },