mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-08 20:47:44 +00:00
627449c0da
FM tuning is performed rarely, usually only when switching stations on the radio, or at irregular intervals. Most of the time, the FM tune thread is idle. This patch converts the FM tune thread to be a |LazyIdleThread|. It get's cleaned up after a small timeout (currently 5 seconds), so it won't occupy resources while the user is actually listening to the radio.
1259 lines
34 KiB
C++
1259 lines
34 KiB
C++
/* -*- 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 "mozilla/ClearOnShutdown.h"
|
|
#include "nsIAudioManager.h"
|
|
#include "AudioManager.h"
|
|
#include "nsDOMClassInfo.h"
|
|
#include "mozilla/LazyIdleThread.h"
|
|
#include "mozilla/Preferences.h"
|
|
#include "mozilla/dom/FMRadioChild.h"
|
|
#include "mozilla/dom/ScriptSettings.h"
|
|
#include "nsIObserverService.h"
|
|
#include "nsISettingsService.h"
|
|
#include "nsJSUtils.h"
|
|
#include "mozilla/dom/BindingUtils.h"
|
|
#include "mozilla/dom/SettingChangeNotificationBinding.h"
|
|
|
|
#define TUNE_THREAD_TIMEOUT_MS 5000
|
|
|
|
#define BAND_87500_108000_kHz 1
|
|
#define BAND_76000_108000_kHz 2
|
|
#define BAND_76000_90000_kHz 3
|
|
|
|
#define MOZSETTINGS_CHANGED_ID "mozsettings-changed"
|
|
#define SETTING_KEY_AIRPLANEMODE_ENABLED "airplaneMode.enabled"
|
|
|
|
#define DOM_PARSED_RDS_GROUPS ((0x2 << 30) | (0x3 << 4) | (0x3 << 0))
|
|
|
|
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)
|
|
, mHasReadAirplaneModeSetting(false)
|
|
, mAirplaneModeEnabled(false)
|
|
, mRDSEnabled(false)
|
|
, mPendingRequest(nullptr)
|
|
, mObserverList(FMRadioEventObserverList())
|
|
, mRDSGroupMask(0)
|
|
, mLastPI(0)
|
|
, mPI(0)
|
|
, mPTY(0)
|
|
, mPISet(false)
|
|
, mPTYSet(false)
|
|
, mRDSLock("FMRadioService::mRDSLock")
|
|
, mPSNameState(0)
|
|
, mRadiotextAB(false)
|
|
, mRDSGroupSet(false)
|
|
, mPSNameSet(false)
|
|
, mRadiotextSet(false)
|
|
{
|
|
memset(mPSName, 0, sizeof(mPSName));
|
|
memset(mRadiotext, 0, sizeof(mRadiotext));
|
|
memset(mTempPSName, 0, sizeof(mTempPSName));
|
|
memset(mTempRadiotext, 0, sizeof(mTempRadiotext));
|
|
|
|
// 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;
|
|
}
|
|
|
|
mChannelWidthInKHz = Preferences::GetInt("dom.fmradio.channelWidth", 100);
|
|
switch (mChannelWidthInKHz) {
|
|
case 50:
|
|
case 100:
|
|
case 200:
|
|
break;
|
|
default:
|
|
NS_WARNING("Invalid channel width specified in dom.fmradio.channelwidth");
|
|
mChannelWidthInKHz = 100;
|
|
break;
|
|
}
|
|
|
|
mPreemphasis = Preferences::GetInt("dom.fmradio.preemphasis", 50);
|
|
switch (mPreemphasis) {
|
|
// values in microseconds
|
|
case 0:
|
|
case 50:
|
|
case 75:
|
|
break;
|
|
default:
|
|
NS_WARNING("Invalid preemphasis specified in dom.fmradio.preemphasis");
|
|
mPreemphasis = 50;
|
|
break;
|
|
}
|
|
|
|
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
|
|
|
|
if (obs && NS_FAILED(obs->AddObserver(this,
|
|
MOZSETTINGS_CHANGED_ID,
|
|
/* useWeak */ false))) {
|
|
NS_WARNING("Failed to add settings change observer!");
|
|
}
|
|
|
|
RegisterFMRadioObserver(this);
|
|
RegisterFMRadioRDSObserver(this);
|
|
}
|
|
|
|
FMRadioService::~FMRadioService()
|
|
{
|
|
UnregisterFMRadioRDSObserver(this);
|
|
UnregisterFMRadioObserver(this);
|
|
}
|
|
|
|
class EnableRunnable MOZ_FINAL : public nsRunnable
|
|
{
|
|
public:
|
|
EnableRunnable(uint32_t aUpperLimit, uint32_t aLowerLimit, uint32_t aSpaceType, uint32_t aPreemphasis)
|
|
: mUpperLimit(aUpperLimit)
|
|
, mLowerLimit(aLowerLimit)
|
|
, mSpaceType(aSpaceType)
|
|
, mPreemphasis(aPreemphasis)
|
|
{
|
|
}
|
|
|
|
NS_IMETHOD Run()
|
|
{
|
|
FMRadioSettings info;
|
|
info.upperLimit() = mUpperLimit;
|
|
info.lowerLimit() = mLowerLimit;
|
|
info.spaceType() = mSpaceType;
|
|
info.preEmphasis() = mPreemphasis;
|
|
|
|
EnableFMRadio(info);
|
|
|
|
FMRadioService* fmRadioService = FMRadioService::Singleton();
|
|
if (!fmRadioService->mTuneThread) {
|
|
// SeekRunnable and SetFrequencyRunnable run on this thread. These
|
|
// call ioctls that can stall the main thread, so we run them here.
|
|
fmRadioService->mTuneThread = new LazyIdleThread(
|
|
TUNE_THREAD_TIMEOUT_MS, NS_LITERAL_CSTRING("FM Tuning"));
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
private:
|
|
uint32_t mUpperLimit;
|
|
uint32_t mLowerLimit;
|
|
uint32_t mSpaceType;
|
|
uint32_t mPreemphasis;
|
|
};
|
|
|
|
/**
|
|
* Read the airplane-mode setting, if the airplane-mode is not enabled, we
|
|
* enable the FM radio.
|
|
*/
|
|
class ReadAirplaneModeSettingTask MOZ_FINAL : public nsISettingsServiceCallback
|
|
{
|
|
public:
|
|
NS_DECL_ISUPPORTS
|
|
|
|
ReadAirplaneModeSettingTask(nsRefPtr<FMRadioReplyRunnable> aPendingRequest)
|
|
: mPendingRequest(aPendingRequest) { }
|
|
|
|
NS_IMETHOD
|
|
Handle(const nsAString& aName, JS::Handle<JS::Value> aResult)
|
|
{
|
|
FMRadioService* fmRadioService = FMRadioService::Singleton();
|
|
MOZ_ASSERT(mPendingRequest == fmRadioService->mPendingRequest);
|
|
|
|
fmRadioService->mHasReadAirplaneModeSetting = true;
|
|
|
|
if (!aResult.isBoolean()) {
|
|
// Failed to read the setting value, set the state back to Disabled.
|
|
fmRadioService->TransitionState(
|
|
ErrorResponse(NS_LITERAL_STRING("Unexpected error")), Disabled);
|
|
return NS_OK;
|
|
}
|
|
|
|
fmRadioService->mAirplaneModeEnabled = aResult.toBoolean();
|
|
if (!fmRadioService->mAirplaneModeEnabled) {
|
|
EnableRunnable* runnable =
|
|
new EnableRunnable(fmRadioService->mUpperBoundInKHz,
|
|
fmRadioService->mLowerBoundInKHz,
|
|
fmRadioService->mChannelWidthInKHz,
|
|
fmRadioService->mPreemphasis);
|
|
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<FMRadioReplyRunnable> mPendingRequest;
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(ReadAirplaneModeSettingTask, nsISettingsServiceCallback)
|
|
|
|
class DisableRunnable MOZ_FINAL : public nsRunnable
|
|
{
|
|
public:
|
|
DisableRunnable() { }
|
|
|
|
NS_IMETHOD Run()
|
|
{
|
|
FMRadioService* fmRadioService = FMRadioService::Singleton();
|
|
if (fmRadioService->mTuneThread) {
|
|
fmRadioService->mTuneThread->Shutdown();
|
|
fmRadioService->mTuneThread = nullptr;
|
|
}
|
|
// Fix Bug 796733. DisableFMRadio should be called before
|
|
// SetFmRadioAudioEnabled to prevent the annoying beep sound.
|
|
DisableFMRadio();
|
|
fmRadioService->EnableAudio(false);
|
|
|
|
return NS_OK;
|
|
}
|
|
};
|
|
|
|
class SetFrequencyRunnable MOZ_FINAL : public nsRunnable
|
|
{
|
|
public:
|
|
SetFrequencyRunnable(int32_t aFrequency)
|
|
: mFrequency(aFrequency) { }
|
|
|
|
NS_IMETHOD Run()
|
|
{
|
|
SetFMRadioFrequency(mFrequency);
|
|
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;
|
|
};
|
|
|
|
class NotifyRunnable MOZ_FINAL : public nsRunnable
|
|
{
|
|
public:
|
|
NotifyRunnable(FMRadioEventType aType) : mType(aType) { }
|
|
|
|
NS_IMETHOD Run()
|
|
{
|
|
FMRadioService::Singleton()->NotifyFMRadioEvent(mType);
|
|
return NS_OK;
|
|
}
|
|
|
|
private:
|
|
FMRadioEventType mType;
|
|
};
|
|
|
|
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()) {
|
|
DoDisable();
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
FMRadioService::EnableAudio(bool aAudioEnabled)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
|
|
|
|
nsCOMPtr<nsIAudioManager> audioManager =
|
|
do_GetService("@mozilla.org/telephony/audiomanager;1");
|
|
if (!audioManager) {
|
|
return;
|
|
}
|
|
|
|
bool audioEnabled;
|
|
audioManager->GetFmRadioAudioEnabled(&audioEnabled);
|
|
if (audioEnabled != aAudioEnabled) {
|
|
audioManager->SetFmRadioAudioEnabled(aAudioEnabled);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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();
|
|
}
|
|
|
|
bool
|
|
FMRadioService::IsRDSEnabled() const
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
|
|
return mRDSEnabled;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
Nullable<unsigned short>
|
|
FMRadioService::GetPi() const
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
|
|
if (!mPISet) {
|
|
return Nullable<unsigned short>();
|
|
}
|
|
return Nullable<unsigned short>(mPI);
|
|
}
|
|
|
|
Nullable<uint8_t>
|
|
FMRadioService::GetPty() const
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
|
|
if (!mPTYSet) {
|
|
return Nullable<uint8_t>();
|
|
}
|
|
return Nullable<uint8_t>(mPTY);
|
|
}
|
|
|
|
bool
|
|
FMRadioService::GetPs(nsString& aPSName)
|
|
{
|
|
MutexAutoLock lock(mRDSLock);
|
|
if (mPSNameSet) {
|
|
aPSName = nsString(mPSName);
|
|
}
|
|
return mPSNameSet;
|
|
}
|
|
|
|
bool
|
|
FMRadioService::GetRt(nsString& aRadiotext)
|
|
{
|
|
MutexAutoLock lock(mRDSLock);
|
|
if (mRadiotextSet) {
|
|
aRadiotext = nsString(mRadiotext);
|
|
}
|
|
return mRadiotextSet;
|
|
}
|
|
|
|
bool
|
|
FMRadioService::GetRdsgroup(uint64_t& aRDSGroup)
|
|
{
|
|
MutexAutoLock lock(mRDSLock);
|
|
aRDSGroup = mRDSGroup;
|
|
return mRDSGroupSet;
|
|
}
|
|
|
|
void
|
|
FMRadioService::Enable(double aFrequencyInMHz,
|
|
FMRadioReplyRunnable* 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 (mHasReadAirplaneModeSetting && mAirplaneModeEnabled) {
|
|
aReplyRunnable->SetReply(ErrorResponse(
|
|
NS_LITERAL_STRING("Airplane mode currently enabled")));
|
|
NS_DispatchToMainThread(aReplyRunnable);
|
|
return;
|
|
}
|
|
|
|
SetState(Enabling);
|
|
// Cache the enable request just in case disable() is called
|
|
// while the FM radio HW is being enabled.
|
|
mPendingRequest = aReplyRunnable;
|
|
|
|
// Cache the frequency value, and set it after the FM radio HW is enabled
|
|
mPendingFrequencyInKHz = roundedFrequency;
|
|
|
|
if (!mHasReadAirplaneModeSetting) {
|
|
nsCOMPtr<nsISettingsService> settings =
|
|
do_GetService("@mozilla.org/settingsService;1");
|
|
|
|
nsCOMPtr<nsISettingsServiceLock> settingsLock;
|
|
nsresult rv = settings->CreateLock(nullptr, getter_AddRefs(settingsLock));
|
|
if (NS_FAILED(rv)) {
|
|
TransitionState(ErrorResponse(
|
|
NS_LITERAL_STRING("Can't create settings lock")), Disabled);
|
|
return;
|
|
}
|
|
|
|
nsRefPtr<ReadAirplaneModeSettingTask> callback =
|
|
new ReadAirplaneModeSettingTask(mPendingRequest);
|
|
|
|
rv = settingsLock->Get(SETTING_KEY_AIRPLANEMODE_ENABLED, callback);
|
|
if (NS_FAILED(rv)) {
|
|
TransitionState(ErrorResponse(
|
|
NS_LITERAL_STRING("Can't get settings lock")), Disabled);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
NS_DispatchToMainThread(new EnableRunnable(mUpperBoundInKHz,
|
|
mLowerBoundInKHz,
|
|
mChannelWidthInKHz,
|
|
mPreemphasis));
|
|
}
|
|
|
|
void
|
|
FMRadioService::Disable(FMRadioReplyRunnable* aReplyRunnable)
|
|
{
|
|
// When airplane-mode is enabled, we will call this function from
|
|
// FMRadioService::Observe without passing a FMRadioReplyRunnable,
|
|
// so we have to check if |aReplyRunnable| is null before we dispatch it.
|
|
MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
|
|
|
|
switch (mState) {
|
|
case Disabling:
|
|
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<FMRadioReplyRunnable> 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 airplane mode settings yet we won't enable the
|
|
// FM radio HW, so fail the disable request immediately.
|
|
if (!mHasReadAirplaneModeSetting) {
|
|
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,
|
|
FMRadioReplyRunnable* 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;
|
|
}
|
|
|
|
mTuneThread->Dispatch(new SetFrequencyRunnable(roundedFrequency),
|
|
nsIThread::DISPATCH_NORMAL);
|
|
|
|
aReplyRunnable->SetReply(SuccessResponse());
|
|
NS_DispatchToMainThread(aReplyRunnable);
|
|
}
|
|
|
|
void
|
|
FMRadioService::Seek(FMRadioSeekDirection aDirection,
|
|
FMRadioReplyRunnable* 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;
|
|
|
|
mTuneThread->Dispatch(new SeekRunnable(aDirection), nsIThread::DISPATCH_NORMAL);
|
|
}
|
|
|
|
void
|
|
FMRadioService::CancelSeek(FMRadioReplyRunnable* 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);
|
|
}
|
|
|
|
void
|
|
FMRadioService::SetRDSGroupMask(uint32_t aRDSGroupMask)
|
|
{
|
|
mRDSGroupMask = aRDSGroupMask;
|
|
if (IsFMRadioOn() && mRDSEnabled) {
|
|
bool enabled = hal::EnableRDS(mRDSGroupMask | DOM_PARSED_RDS_GROUPS);
|
|
MOZ_ASSERT(enabled);
|
|
}
|
|
}
|
|
|
|
void
|
|
FMRadioService::EnableRDS(FMRadioReplyRunnable* aReplyRunnable)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
|
|
MOZ_ASSERT(aReplyRunnable);
|
|
|
|
if (IsFMRadioOn()) {
|
|
if (!hal::EnableRDS(mRDSGroupMask | DOM_PARSED_RDS_GROUPS)) {
|
|
aReplyRunnable->SetReply(
|
|
ErrorResponse(NS_LITERAL_STRING("Could not enable RDS")));
|
|
NS_DispatchToMainThread(aReplyRunnable);
|
|
return;
|
|
}
|
|
}
|
|
|
|
mRDSEnabled = true;
|
|
|
|
aReplyRunnable->SetReply(SuccessResponse());
|
|
NS_DispatchToMainThread(aReplyRunnable);
|
|
NS_DispatchToMainThread(new NotifyRunnable(RDSEnabledChanged));
|
|
}
|
|
|
|
void
|
|
FMRadioService::DisableRDS(FMRadioReplyRunnable* aReplyRunnable)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
|
|
MOZ_ASSERT(aReplyRunnable);
|
|
|
|
if (IsFMRadioOn()) {
|
|
hal::DisableRDS();
|
|
}
|
|
|
|
aReplyRunnable->SetReply(SuccessResponse());
|
|
NS_DispatchToMainThread(aReplyRunnable);
|
|
|
|
if (mRDSEnabled) {
|
|
mRDSEnabled = false;
|
|
NS_DispatchToMainThread(new NotifyRunnable(RDSEnabledChanged));
|
|
}
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
FMRadioService::Observe(nsISupports* aSubject,
|
|
const char* aTopic,
|
|
const char16_t* aData)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(sFMRadioService);
|
|
|
|
if (strcmp(aTopic, MOZSETTINGS_CHANGED_ID) != 0) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// The string that we're interested in will be a JSON string looks like:
|
|
// {"key":"airplaneMode.enabled","value":true}
|
|
AutoJSAPI jsapi;
|
|
jsapi.Init();
|
|
JSContext* cx = jsapi.cx();
|
|
RootedDictionary<dom::SettingChangeNotification> setting(cx);
|
|
if (!WrappedJSToDictionary(cx, aSubject, setting)) {
|
|
return NS_OK;
|
|
}
|
|
if (!setting.mKey.EqualsASCII(SETTING_KEY_AIRPLANEMODE_ENABLED)) {
|
|
return NS_OK;
|
|
}
|
|
if (!setting.mValue.isBoolean()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
mAirplaneModeEnabled = setting.mValue.toBoolean();
|
|
mHasReadAirplaneModeSetting = true;
|
|
|
|
// Disable the FM radio HW if Airplane mode is enabled.
|
|
if (mAirplaneModeEnabled) {
|
|
Disable(nullptr);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
FMRadioService::NotifyFMRadioEvent(FMRadioEventType aType)
|
|
{
|
|
mObserverList.Broadcast(aType);
|
|
}
|
|
|
|
void
|
|
FMRadioService::Notify(const FMRadioOperationInformation& aInfo)
|
|
{
|
|
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);
|
|
|
|
// Bug 949855: enable audio after the FM radio HW is enabled, to make sure
|
|
// 'hw.fm.isAnalog' could be detected as |true| during first time launch.
|
|
// This case is for audio output on analog path, i.e. 'ro.moz.fm.noAnalog'
|
|
// is not |true|.
|
|
EnableAudio(true);
|
|
|
|
// 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);
|
|
|
|
if (mRDSEnabled) {
|
|
mRDSEnabled = hal::EnableRDS(mRDSGroupMask | DOM_PARSED_RDS_GROUPS);
|
|
if (!mRDSEnabled) {
|
|
NotifyFMRadioEvent(RDSEnabledChanged);
|
|
}
|
|
}
|
|
break;
|
|
case FM_RADIO_OPERATION_DISABLE:
|
|
MOZ_ASSERT(mState == Disabling);
|
|
|
|
mPISet = false;
|
|
mPTYSet = false;
|
|
memset(mPSName, 0, sizeof(mPSName));
|
|
memset(mRadiotext, 0, sizeof(mRadiotext));
|
|
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;
|
|
case FM_RADIO_OPERATION_TUNE:
|
|
UpdateFrequency();
|
|
break;
|
|
default:
|
|
MOZ_CRASH();
|
|
}
|
|
}
|
|
|
|
/* This is defined by the RDS standard */
|
|
static const uint16_t sRDSToUnicodeMap[256] = {
|
|
// The lower half differs from ASCII in 0x1F, 0x24, 0x5E, 0x7E
|
|
// Most control characters are replaced with 0x20 (space)
|
|
// 0x0-
|
|
0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020,
|
|
0x0020, 0x0009, 0x000A, 0x000B, 0x0020, 0x00D0, 0x0020, 0x0020,
|
|
|
|
// 0x1-
|
|
0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020,
|
|
0x0020, 0x0020, 0x0020, 0x001B, 0x0020, 0x0020, 0x0020, 0x00AD,
|
|
|
|
// 0x2-
|
|
0x0020, 0x0021, 0x0022, 0x0023, 0x00A4, 0x0025, 0x0026, 0x0027,
|
|
0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
|
|
|
|
// 0x3-
|
|
0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
|
|
0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
|
|
|
|
// 0x4-
|
|
0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
|
|
0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
|
|
|
|
// 0x5-
|
|
0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
|
|
0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x2015, 0x005F,
|
|
|
|
// 0x6-
|
|
0x2551, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
|
|
0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
|
|
|
|
// 0x7-
|
|
0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
|
|
0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x00AF, 0x007F,
|
|
|
|
// 0x8-
|
|
0x00E1, 0x00E0, 0x00E9, 0x00E8, 0x00ED, 0x00EC, 0x00F3, 0x00F2,
|
|
0x00FA, 0x00F9, 0x00D1, 0x00C7, 0x015E, 0x00DF, 0x00A1, 0x0132,
|
|
|
|
// 0x9-
|
|
0x00E2, 0x00E4, 0x00EA, 0x00EB, 0x00EE, 0x00EF, 0x00F4, 0x00F6,
|
|
0x00FB, 0x00FC, 0x00F1, 0x00E7, 0x015F, 0x011F, 0x0131, 0x0133,
|
|
|
|
// 0xA-
|
|
0x00AA, 0x03B1, 0x00A9, 0x2030, 0x011E, 0x011B, 0x0148, 0x0151,
|
|
0x03C0, 0x20AC, 0x00A3, 0x0024, 0x2190, 0x2191, 0x2192, 0x2193,
|
|
|
|
// 0xB-
|
|
0x00BA, 0x00B9, 0x00B2, 0x00B3, 0x00B1, 0x0130, 0x0144, 0x0171,
|
|
0x03BC, 0x00BF, 0x00F7, 0x00B0, 0x00BC, 0x00BD, 0x00BE, 0x00A7,
|
|
|
|
// 0xC-
|
|
0x00C1, 0x00C0, 0x00C9, 0x00C8, 0x00CD, 0x00CC, 0x00D3, 0x00D2,
|
|
0x00DA, 0x00D9, 0x0158, 0x010C, 0x0160, 0x017D, 0x00D0, 0x013F,
|
|
|
|
// 0xD-
|
|
0x00C2, 0x00C4, 0x00CA, 0x00CB, 0x00CE, 0x00CF, 0x00D4, 0x00D6,
|
|
0x00DB, 0x00DC, 0x0159, 0x010D, 0x0161, 0x017E, 0x0111, 0x0140,
|
|
|
|
// 0xE-
|
|
0x00C3, 0x00C5, 0x00C6, 0x0152, 0x0177, 0x00DD, 0x00D5, 0x00D8,
|
|
0x00DE, 0x014A, 0x0154, 0x0106, 0x015A, 0x0179, 0x0166, 0x00F0,
|
|
|
|
// 0xF-
|
|
0x00E3, 0x00E5, 0x00E6, 0x0153, 0x0175, 0x00FD, 0x00F5, 0x00F8,
|
|
0x00FE, 0x014B, 0x0155, 0x0107, 0x015B, 0x017A, 0x0167, 0x0020,
|
|
};
|
|
|
|
void
|
|
FMRadioService::Notify(const FMRadioRDSGroup& aRDSGroup)
|
|
{
|
|
uint16_t blocks[4];
|
|
blocks[0] = aRDSGroup.blockA();
|
|
blocks[1] = aRDSGroup.blockB();
|
|
blocks[2] = aRDSGroup.blockC();
|
|
blocks[3] = aRDSGroup.blockD();
|
|
|
|
/* Bit 11 in block B determines whether this is a type B group. */
|
|
uint16_t lastPI = blocks[1] & (1 << 11) ? blocks[2] : mLastPI;
|
|
|
|
/* Update PI if it's not set or if we get two PI with the new value. */
|
|
if ((mPI != blocks[0] && lastPI == blocks[0]) || !mPISet) {
|
|
mPI = blocks[0];
|
|
if (!mPISet) {
|
|
mPSNameState = 0;
|
|
mRadiotextState = 0;
|
|
memset(mTempPSName, 0, sizeof(mTempPSName));
|
|
memset(mTempRadiotext, 0, sizeof(mTempRadiotext));
|
|
}
|
|
mPISet = true;
|
|
NS_DispatchToMainThread(new NotifyRunnable(PIChanged));
|
|
}
|
|
mLastPI = blocks[0];
|
|
|
|
/* PTY is also updated using the same logic as PI */
|
|
uint16_t pty = (blocks[1] >> 5) & 0x1F;
|
|
if ((mPTY != pty && pty == mLastPTY) || !mPTYSet) {
|
|
mPTY = pty;
|
|
mPTYSet = true;
|
|
NS_DispatchToMainThread(new NotifyRunnable(PTYChanged));
|
|
}
|
|
mLastPTY = pty;
|
|
|
|
uint16_t grouptype = blocks[1] >> 11;
|
|
switch (grouptype) {
|
|
case 0: // 0a
|
|
case 1: // 0b
|
|
{
|
|
uint16_t segmentAddr = (blocks[1] & 0x3);
|
|
// mPSNameState is a bitmask that lets us ensure all segments
|
|
// are received before updating the PS name.
|
|
if (!segmentAddr) {
|
|
mPSNameState = 1;
|
|
} else {
|
|
mPSNameState |= 1 << segmentAddr;
|
|
}
|
|
|
|
uint16_t offset = segmentAddr << 1;
|
|
mTempPSName[offset] = sRDSToUnicodeMap[blocks[3] >> 8];
|
|
mTempPSName[offset + 1] = sRDSToUnicodeMap[blocks[3] & 0xFF];
|
|
|
|
if (mPSNameState != 0xF) {
|
|
break;
|
|
}
|
|
|
|
mPSNameState = 0;
|
|
if (memcmp(mTempPSName, mPSName, sizeof(mTempPSName))) {
|
|
MutexAutoLock lock(mRDSLock);
|
|
mPSNameSet = true;
|
|
memcpy(mPSName, mTempPSName, sizeof(mTempPSName));
|
|
NS_DispatchToMainThread(new NotifyRunnable(PSChanged));
|
|
}
|
|
break;
|
|
}
|
|
case 4: // 2a Radiotext
|
|
{
|
|
uint16_t segmentAddr = (blocks[1] & 0xF);
|
|
bool textAB = blocks[1] & (1 << 5);
|
|
if (textAB != mRadiotextAB) {
|
|
mRadiotextState = 0;
|
|
memset(mTempRadiotext, 0, sizeof(mTempRadiotext));
|
|
mRadiotextAB = textAB;
|
|
MutexAutoLock lock(mRDSLock);
|
|
memset(mRadiotext, 0, sizeof(mRadiotext));
|
|
NS_DispatchToMainThread(new NotifyRunnable(RadiotextChanged));
|
|
}
|
|
|
|
// mRadiotextState is a bitmask that lets us ensure all segments
|
|
// are received before updating the radiotext.
|
|
if (!segmentAddr) {
|
|
mRadiotextState = 1;
|
|
} else {
|
|
mRadiotextState |= 1 << segmentAddr;
|
|
}
|
|
|
|
uint8_t segment[4];
|
|
segment[0] = blocks[2] >> 8;
|
|
segment[1] = blocks[2] & 0xFF;
|
|
segment[2] = blocks[3] >> 8;
|
|
segment[3] = blocks[3] & 0xFF;
|
|
|
|
uint16_t offset = segmentAddr << 2;
|
|
bool done = false;
|
|
for (int i = 0; i < 4; i++) {
|
|
if (segment[i] == '\r') {
|
|
mTempRadiotext[offset++] = 0;
|
|
done = true;
|
|
} else {
|
|
mTempRadiotext[offset++] = sRDSToUnicodeMap[segment[i]];
|
|
}
|
|
}
|
|
if (offset == 64) {
|
|
done = true;
|
|
}
|
|
|
|
if (!done ||
|
|
(mRadiotextState + 1) != (1 << ((blocks[1] & 0xF) + 1)) ||
|
|
!memcmp(mTempRadiotext, mRadiotext, sizeof(mTempRadiotext))) {
|
|
break;
|
|
}
|
|
|
|
MutexAutoLock lock(mRDSLock);
|
|
mRadiotextSet = true;
|
|
memcpy(mRadiotext, mTempRadiotext, sizeof(mTempRadiotext));
|
|
NS_DispatchToMainThread(new NotifyRunnable(RadiotextChanged));
|
|
break;
|
|
}
|
|
case 5: // 2b Radiotext
|
|
{
|
|
uint16_t segmentAddr = (blocks[1] & 0xF);
|
|
bool textAB = blocks[1] & (1 << 5);
|
|
if (textAB != mRadiotextAB) {
|
|
mRadiotextState = 0;
|
|
memset(mTempRadiotext, 0, sizeof(mTempRadiotext));
|
|
mRadiotextAB = textAB;
|
|
MutexAutoLock lock(mRDSLock);
|
|
memset(mRadiotext, 0, sizeof(mRadiotext));
|
|
NS_DispatchToMainThread(new NotifyRunnable(RadiotextChanged));
|
|
}
|
|
|
|
if (!segmentAddr) {
|
|
mRadiotextState = 1;
|
|
} else {
|
|
mRadiotextState |= 1 << segmentAddr;
|
|
}
|
|
uint8_t segment[2];
|
|
segment[0] = blocks[3] >> 8;
|
|
segment[1] = blocks[3] & 0xFF;
|
|
|
|
uint16_t offset = segmentAddr << 1;
|
|
bool done = false;
|
|
for (int i = 0; i < 2; i++) {
|
|
if (segment[i] == '\r') {
|
|
mTempRadiotext[offset++] = 0;
|
|
done = true;
|
|
} else {
|
|
mTempRadiotext[offset++] = sRDSToUnicodeMap[segment[i]];
|
|
}
|
|
}
|
|
if (offset == 32) {
|
|
done = true;
|
|
}
|
|
|
|
if (!done ||
|
|
(mRadiotextState + 1) != (1 << ((blocks[1] & 0xF) + 1)) ||
|
|
!memcmp(mTempRadiotext, mRadiotext, sizeof(mTempRadiotext))) {
|
|
break;
|
|
}
|
|
|
|
MutexAutoLock lock(mRDSLock);
|
|
mRadiotextSet = true;
|
|
memcpy(mRadiotext, mTempRadiotext, sizeof(mTempRadiotext));
|
|
NS_DispatchToMainThread(new NotifyRunnable(RadiotextChanged));
|
|
break;
|
|
}
|
|
case 31: // 15b Fast Tuning and Switching
|
|
{
|
|
uint16_t secondPty = (blocks[3] >> 5) & 0x1F;
|
|
if (pty == mPTY || pty != secondPty) {
|
|
break;
|
|
}
|
|
mPTY = pty;
|
|
NS_DispatchToMainThread(new NotifyRunnable(PTYChanged));
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Only notify users of raw RDS groups that they're interested in.
|
|
// We always receive DOM_PARSED_RDS_GROUPS when RDS is enabled.
|
|
if (!(mRDSGroupMask & (1 << grouptype))) {
|
|
return;
|
|
}
|
|
|
|
uint64_t newgroup = blocks[0];
|
|
newgroup <<= 16;
|
|
newgroup |= blocks[1];
|
|
newgroup <<= 16;
|
|
newgroup |= blocks[2];
|
|
newgroup <<= 16;
|
|
newgroup |= blocks[3];
|
|
|
|
MutexAutoLock lock(mRDSLock);
|
|
mRDSGroup = newgroup;
|
|
mRDSGroupSet = true;
|
|
NS_DispatchToMainThread(new NotifyRunnable(NewRDSGroup));
|
|
}
|
|
|
|
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);
|
|
mPISet = false;
|
|
mPTYSet = false;
|
|
memset(mPSName, 0, sizeof(mPSName));
|
|
memset(mRadiotext, 0, sizeof(mRadiotext));
|
|
mRDSGroupSet = false;
|
|
mPSNameSet = false;
|
|
mRadiotextSet = false;
|
|
}
|
|
}
|
|
|
|
// static
|
|
FMRadioService*
|
|
FMRadioService::Singleton()
|
|
{
|
|
MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (!sFMRadioService) {
|
|
sFMRadioService = new FMRadioService();
|
|
ClearOnShutdown(&sFMRadioService);
|
|
}
|
|
|
|
return sFMRadioService;
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(FMRadioService, nsIObserver)
|
|
|
|
END_FMRADIO_NAMESPACE
|
|
|