2012-09-19 08:23:33 -07:00
|
|
|
/* 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");
|
|
|
|
|
2012-10-31 09:13:28 -07:00
|
|
|
this.EXPORTED_SYMBOLS = ["DOMFMRadioParent"];
|
2012-09-19 08:23:33 -07:00
|
|
|
|
2012-10-31 09:13:28 -07:00
|
|
|
this.DOMFMRadioParent = {
|
2012-10-11 18:31:39 -07:00
|
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
|
|
|
|
Ci.nsISettingsServiceCallback]),
|
|
|
|
|
2012-09-19 08:23:33 -07:00
|
|
|
_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"
|
|
|
|
];
|
|
|
|
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() {
|
|
|
|
debug("FM Radio is enabled!");
|
|
|
|
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", { });
|
|
|
|
});
|
|
|
|
|
|
|
|
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;
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
DOMFMRadioParent.init();
|
|
|
|
|