michael@0: /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "FMRadioService.h" michael@0: #include "mozilla/Hal.h" michael@0: #include "nsIAudioManager.h" michael@0: #include "AudioManager.h" michael@0: #include "nsDOMClassInfo.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/dom/FMRadioChild.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsISettingsService.h" michael@0: #include "nsJSUtils.h" michael@0: #include "nsCxPusher.h" michael@0: michael@0: #define BAND_87500_108000_kHz 1 michael@0: #define BAND_76000_108000_kHz 2 michael@0: #define BAND_76000_90000_kHz 3 michael@0: michael@0: #define CHANNEL_WIDTH_200KHZ 200 michael@0: #define CHANNEL_WIDTH_100KHZ 100 michael@0: #define CHANNEL_WIDTH_50KHZ 50 michael@0: michael@0: #define MOZSETTINGS_CHANGED_ID "mozsettings-changed" michael@0: #define SETTING_KEY_AIRPLANEMODE_ENABLED "airplaneMode.enabled" michael@0: michael@0: using namespace mozilla::hal; michael@0: using mozilla::Preferences; michael@0: michael@0: BEGIN_FMRADIO_NAMESPACE michael@0: michael@0: // static michael@0: IFMRadioService* michael@0: IFMRadioService::Singleton() michael@0: { michael@0: if (XRE_GetProcessType() != GeckoProcessType_Default) { michael@0: return FMRadioChild::Singleton(); michael@0: } else { michael@0: return FMRadioService::Singleton(); michael@0: } michael@0: } michael@0: michael@0: StaticRefPtr FMRadioService::sFMRadioService; michael@0: michael@0: FMRadioService::FMRadioService() michael@0: : mPendingFrequencyInKHz(0) michael@0: , mState(Disabled) michael@0: , mHasReadAirplaneModeSetting(false) michael@0: , mAirplaneModeEnabled(false) michael@0: , mPendingRequest(nullptr) michael@0: , mObserverList(FMRadioEventObserverList()) michael@0: { michael@0: michael@0: // Read power state and frequency from Hal. michael@0: mEnabled = IsFMRadioOn(); michael@0: if (mEnabled) { michael@0: mPendingFrequencyInKHz = GetFMRadioFrequency(); michael@0: SetState(Enabled); michael@0: } michael@0: michael@0: switch (Preferences::GetInt("dom.fmradio.band", BAND_87500_108000_kHz)) { michael@0: case BAND_76000_90000_kHz: michael@0: mUpperBoundInKHz = 90000; michael@0: mLowerBoundInKHz = 76000; michael@0: break; michael@0: case BAND_76000_108000_kHz: michael@0: mUpperBoundInKHz = 108000; michael@0: mLowerBoundInKHz = 76000; michael@0: break; michael@0: case BAND_87500_108000_kHz: michael@0: default: michael@0: mUpperBoundInKHz = 108000; michael@0: mLowerBoundInKHz = 87500; michael@0: break; michael@0: } michael@0: michael@0: switch (Preferences::GetInt("dom.fmradio.channelWidth", michael@0: CHANNEL_WIDTH_100KHZ)) { michael@0: case CHANNEL_WIDTH_200KHZ: michael@0: mChannelWidthInKHz = 200; michael@0: break; michael@0: case CHANNEL_WIDTH_50KHZ: michael@0: mChannelWidthInKHz = 50; michael@0: break; michael@0: case CHANNEL_WIDTH_100KHZ: michael@0: default: michael@0: mChannelWidthInKHz = 100; michael@0: break; michael@0: } michael@0: michael@0: nsCOMPtr obs = services::GetObserverService(); michael@0: michael@0: if (obs && NS_FAILED(obs->AddObserver(this, michael@0: MOZSETTINGS_CHANGED_ID, michael@0: /* useWeak */ false))) { michael@0: NS_WARNING("Failed to add settings change observer!"); michael@0: } michael@0: michael@0: RegisterFMRadioObserver(this); michael@0: } michael@0: michael@0: FMRadioService::~FMRadioService() michael@0: { michael@0: UnregisterFMRadioObserver(this); michael@0: } michael@0: michael@0: class EnableRunnable MOZ_FINAL : public nsRunnable michael@0: { michael@0: public: michael@0: EnableRunnable(int32_t aUpperLimit, int32_t aLowerLimit, int32_t aSpaceType) michael@0: : mUpperLimit(aUpperLimit) michael@0: , mLowerLimit(aLowerLimit) michael@0: , mSpaceType(aSpaceType) { } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: FMRadioSettings info; michael@0: info.upperLimit() = mUpperLimit; michael@0: info.lowerLimit() = mLowerLimit; michael@0: info.spaceType() = mSpaceType; michael@0: michael@0: EnableFMRadio(info); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: int32_t mUpperLimit; michael@0: int32_t mLowerLimit; michael@0: int32_t mSpaceType; michael@0: }; michael@0: michael@0: /** michael@0: * Read the airplane-mode setting, if the airplane-mode is not enabled, we michael@0: * enable the FM radio. michael@0: */ michael@0: class ReadAirplaneModeSettingTask MOZ_FINAL : public nsISettingsServiceCallback michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: ReadAirplaneModeSettingTask(nsRefPtr aPendingRequest) michael@0: : mPendingRequest(aPendingRequest) { } michael@0: michael@0: NS_IMETHOD michael@0: Handle(const nsAString& aName, JS::Handle aResult) michael@0: { michael@0: FMRadioService* fmRadioService = FMRadioService::Singleton(); michael@0: MOZ_ASSERT(mPendingRequest == fmRadioService->mPendingRequest); michael@0: michael@0: fmRadioService->mHasReadAirplaneModeSetting = true; michael@0: michael@0: if (!aResult.isBoolean()) { michael@0: // Failed to read the setting value, set the state back to Disabled. michael@0: fmRadioService->TransitionState( michael@0: ErrorResponse(NS_LITERAL_STRING("Unexpected error")), Disabled); michael@0: return NS_OK; michael@0: } michael@0: michael@0: fmRadioService->mAirplaneModeEnabled = aResult.toBoolean(); michael@0: if (!fmRadioService->mAirplaneModeEnabled) { michael@0: EnableRunnable* runnable = michael@0: new EnableRunnable(fmRadioService->mUpperBoundInKHz, michael@0: fmRadioService->mLowerBoundInKHz, michael@0: fmRadioService->mChannelWidthInKHz); michael@0: NS_DispatchToMainThread(runnable); michael@0: } else { michael@0: // Airplane mode is enabled, set the state back to Disabled. michael@0: fmRadioService->TransitionState(ErrorResponse( michael@0: NS_LITERAL_STRING("Airplane mode currently enabled")), Disabled); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHOD michael@0: HandleError(const nsAString& aName) michael@0: { michael@0: FMRadioService* fmRadioService = FMRadioService::Singleton(); michael@0: MOZ_ASSERT(mPendingRequest == fmRadioService->mPendingRequest); michael@0: michael@0: fmRadioService->TransitionState(ErrorResponse( michael@0: NS_LITERAL_STRING("Unexpected error")), Disabled); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: nsRefPtr mPendingRequest; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(ReadAirplaneModeSettingTask, nsISettingsServiceCallback) michael@0: michael@0: class DisableRunnable MOZ_FINAL : public nsRunnable michael@0: { michael@0: public: michael@0: DisableRunnable() { } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: // Fix Bug 796733. DisableFMRadio should be called before michael@0: // SetFmRadioAudioEnabled to prevent the annoying beep sound. michael@0: DisableFMRadio(); michael@0: IFMRadioService::Singleton()->EnableAudio(false); michael@0: michael@0: return NS_OK; michael@0: } michael@0: }; michael@0: michael@0: class SetFrequencyRunnable MOZ_FINAL : public nsRunnable michael@0: { michael@0: public: michael@0: SetFrequencyRunnable(int32_t aFrequency) michael@0: : mFrequency(aFrequency) { } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: SetFMRadioFrequency(mFrequency); michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: int32_t mFrequency; michael@0: }; michael@0: michael@0: class SeekRunnable MOZ_FINAL : public nsRunnable michael@0: { michael@0: public: michael@0: SeekRunnable(FMRadioSeekDirection aDirection) : mDirection(aDirection) { } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: switch (mDirection) { michael@0: case FM_RADIO_SEEK_DIRECTION_UP: michael@0: case FM_RADIO_SEEK_DIRECTION_DOWN: michael@0: FMRadioSeek(mDirection); michael@0: break; michael@0: default: michael@0: MOZ_CRASH(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: FMRadioSeekDirection mDirection; michael@0: }; michael@0: michael@0: void michael@0: FMRadioService::TransitionState(const FMRadioResponseType& aResponse, michael@0: FMRadioState aState) michael@0: { michael@0: if (mPendingRequest) { michael@0: mPendingRequest->SetReply(aResponse); michael@0: NS_DispatchToMainThread(mPendingRequest); michael@0: } michael@0: michael@0: SetState(aState); michael@0: } michael@0: michael@0: void michael@0: FMRadioService::SetState(FMRadioState aState) michael@0: { michael@0: mState = aState; michael@0: mPendingRequest = nullptr; michael@0: } michael@0: michael@0: void michael@0: FMRadioService::AddObserver(FMRadioEventObserver* aObserver) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); michael@0: mObserverList.AddObserver(aObserver); michael@0: } michael@0: michael@0: void michael@0: FMRadioService::RemoveObserver(FMRadioEventObserver* aObserver) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); michael@0: mObserverList.RemoveObserver(aObserver); michael@0: michael@0: if (mObserverList.Length() == 0) michael@0: { michael@0: // Turning off the FM radio HW because observer list is empty. michael@0: if (IsFMRadioOn()) { michael@0: NS_DispatchToMainThread(new DisableRunnable()); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: FMRadioService::EnableAudio(bool aAudioEnabled) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: nsCOMPtr audioManager = michael@0: do_GetService("@mozilla.org/telephony/audiomanager;1"); michael@0: if (!audioManager) { michael@0: return; michael@0: } michael@0: michael@0: bool audioEnabled; michael@0: audioManager->GetFmRadioAudioEnabled(&audioEnabled); michael@0: if (audioEnabled != aAudioEnabled) { michael@0: audioManager->SetFmRadioAudioEnabled(aAudioEnabled); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Round the frequency to match the range of frequency and the channel width. If michael@0: * the given frequency is out of range, return 0. For example: michael@0: * - lower: 87500KHz, upper: 108000KHz, channel width: 200KHz michael@0: * 87.6MHz is rounded to 87700KHz michael@0: * 87.58MHz is rounded to 87500KHz michael@0: * 87.49MHz is rounded to 87500KHz michael@0: * 109MHz is not rounded, 0 will be returned michael@0: * michael@0: * We take frequency in MHz to prevent precision losing, and return rounded michael@0: * value in KHz for Gonk using. michael@0: */ michael@0: int32_t michael@0: FMRadioService::RoundFrequency(double aFrequencyInMHz) michael@0: { michael@0: double halfChannelWidthInMHz = mChannelWidthInKHz / 1000.0 / 2; michael@0: michael@0: // Make sure 87.49999MHz would be rounded to the lower bound when michael@0: // the lower bound is 87500KHz. michael@0: if (aFrequencyInMHz < mLowerBoundInKHz / 1000.0 - halfChannelWidthInMHz || michael@0: aFrequencyInMHz > mUpperBoundInKHz / 1000.0 + halfChannelWidthInMHz) { michael@0: return 0; michael@0: } michael@0: michael@0: int32_t partToBeRounded = round(aFrequencyInMHz * 1000) - mLowerBoundInKHz; michael@0: int32_t roundedPart = round(partToBeRounded / (double)mChannelWidthInKHz) * michael@0: mChannelWidthInKHz; michael@0: michael@0: return mLowerBoundInKHz + roundedPart; michael@0: } michael@0: michael@0: bool michael@0: FMRadioService::IsEnabled() const michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); michael@0: return IsFMRadioOn(); michael@0: } michael@0: michael@0: double michael@0: FMRadioService::GetFrequency() const michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); michael@0: if (IsEnabled()) { michael@0: int32_t frequencyInKHz = GetFMRadioFrequency(); michael@0: return frequencyInKHz / 1000.0; michael@0: } michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: double michael@0: FMRadioService::GetFrequencyUpperBound() const michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); michael@0: return mUpperBoundInKHz / 1000.0; michael@0: } michael@0: michael@0: double michael@0: FMRadioService::GetFrequencyLowerBound() const michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); michael@0: return mLowerBoundInKHz / 1000.0; michael@0: } michael@0: michael@0: double michael@0: FMRadioService::GetChannelWidth() const michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); michael@0: return mChannelWidthInKHz / 1000.0; michael@0: } michael@0: michael@0: void michael@0: FMRadioService::Enable(double aFrequencyInMHz, michael@0: FMRadioReplyRunnable* aReplyRunnable) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); michael@0: MOZ_ASSERT(aReplyRunnable); michael@0: michael@0: switch (mState) { michael@0: case Seeking: michael@0: case Enabled: michael@0: aReplyRunnable->SetReply( michael@0: ErrorResponse(NS_LITERAL_STRING("FM radio currently enabled"))); michael@0: NS_DispatchToMainThread(aReplyRunnable); michael@0: return; michael@0: case Disabling: michael@0: aReplyRunnable->SetReply( michael@0: ErrorResponse(NS_LITERAL_STRING("FM radio currently disabling"))); michael@0: NS_DispatchToMainThread(aReplyRunnable); michael@0: return; michael@0: case Enabling: michael@0: aReplyRunnable->SetReply( michael@0: ErrorResponse(NS_LITERAL_STRING("FM radio currently enabling"))); michael@0: NS_DispatchToMainThread(aReplyRunnable); michael@0: return; michael@0: case Disabled: michael@0: break; michael@0: } michael@0: michael@0: int32_t roundedFrequency = RoundFrequency(aFrequencyInMHz); michael@0: michael@0: if (!roundedFrequency) { michael@0: aReplyRunnable->SetReply(ErrorResponse( michael@0: NS_LITERAL_STRING("Frequency is out of range"))); michael@0: NS_DispatchToMainThread(aReplyRunnable); michael@0: return; michael@0: } michael@0: michael@0: if (mHasReadAirplaneModeSetting && mAirplaneModeEnabled) { michael@0: aReplyRunnable->SetReply(ErrorResponse( michael@0: NS_LITERAL_STRING("Airplane mode currently enabled"))); michael@0: NS_DispatchToMainThread(aReplyRunnable); michael@0: return; michael@0: } michael@0: michael@0: SetState(Enabling); michael@0: // Cache the enable request just in case disable() is called michael@0: // while the FM radio HW is being enabled. michael@0: mPendingRequest = aReplyRunnable; michael@0: michael@0: // Cache the frequency value, and set it after the FM radio HW is enabled michael@0: mPendingFrequencyInKHz = roundedFrequency; michael@0: michael@0: if (!mHasReadAirplaneModeSetting) { michael@0: nsCOMPtr settings = michael@0: do_GetService("@mozilla.org/settingsService;1"); michael@0: michael@0: nsCOMPtr settingsLock; michael@0: nsresult rv = settings->CreateLock(nullptr, getter_AddRefs(settingsLock)); michael@0: if (NS_FAILED(rv)) { michael@0: TransitionState(ErrorResponse( michael@0: NS_LITERAL_STRING("Can't create settings lock")), Disabled); michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr callback = michael@0: new ReadAirplaneModeSettingTask(mPendingRequest); michael@0: michael@0: rv = settingsLock->Get(SETTING_KEY_AIRPLANEMODE_ENABLED, callback); michael@0: if (NS_FAILED(rv)) { michael@0: TransitionState(ErrorResponse( michael@0: NS_LITERAL_STRING("Can't get settings lock")), Disabled); michael@0: } michael@0: michael@0: return; michael@0: } michael@0: michael@0: NS_DispatchToMainThread(new EnableRunnable(mUpperBoundInKHz, michael@0: mLowerBoundInKHz, michael@0: mChannelWidthInKHz)); michael@0: } michael@0: michael@0: void michael@0: FMRadioService::Disable(FMRadioReplyRunnable* aReplyRunnable) michael@0: { michael@0: // When airplane-mode is enabled, we will call this function from michael@0: // FMRadioService::Observe without passing a FMRadioReplyRunnable, michael@0: // so we have to check if |aReplyRunnable| is null before we dispatch it. michael@0: MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: switch (mState) { michael@0: case Disabling: michael@0: if (aReplyRunnable) { michael@0: aReplyRunnable->SetReply( michael@0: ErrorResponse(NS_LITERAL_STRING("FM radio currently disabling"))); michael@0: NS_DispatchToMainThread(aReplyRunnable); michael@0: } michael@0: return; michael@0: case Disabled: michael@0: if (aReplyRunnable) { michael@0: aReplyRunnable->SetReply( michael@0: ErrorResponse(NS_LITERAL_STRING("FM radio currently disabled"))); michael@0: NS_DispatchToMainThread(aReplyRunnable); michael@0: } michael@0: return; michael@0: case Enabled: michael@0: case Enabling: michael@0: case Seeking: michael@0: break; michael@0: } michael@0: michael@0: nsRefPtr enablingRequest = mPendingRequest; michael@0: michael@0: // If the FM Radio is currently seeking, no fail-to-seek or similar michael@0: // event will be fired, execute the seek callback manually. michael@0: if (mState == Seeking) { michael@0: TransitionState(ErrorResponse( michael@0: NS_LITERAL_STRING("Seek action is cancelled")), Disabling); michael@0: } michael@0: michael@0: FMRadioState preState = mState; michael@0: SetState(Disabling); michael@0: mPendingRequest = aReplyRunnable; michael@0: michael@0: if (preState == Enabling) { michael@0: // If the radio is currently enabling, we fire the error callback on the michael@0: // enable request immediately. When the radio finishes enabling, we'll call michael@0: // DoDisable and fire the success callback on the disable request. michael@0: enablingRequest->SetReply( michael@0: ErrorResponse(NS_LITERAL_STRING("Enable action is cancelled"))); michael@0: NS_DispatchToMainThread(enablingRequest); michael@0: michael@0: // If we haven't read the airplane mode settings yet we won't enable the michael@0: // FM radio HW, so fail the disable request immediately. michael@0: if (!mHasReadAirplaneModeSetting) { michael@0: SetState(Disabled); michael@0: michael@0: if (aReplyRunnable) { michael@0: aReplyRunnable->SetReply(SuccessResponse()); michael@0: NS_DispatchToMainThread(aReplyRunnable); michael@0: } michael@0: } michael@0: michael@0: return; michael@0: } michael@0: michael@0: DoDisable(); michael@0: } michael@0: michael@0: void michael@0: FMRadioService::DoDisable() michael@0: { michael@0: // To make such codes work: michael@0: // navigator.mozFMRadio.disable(); michael@0: // navigator.mozFMRadio.ondisabled = function() { michael@0: // console.log("We will catch disabled event "); michael@0: // }; michael@0: // we need to call hal::DisableFMRadio() asynchronously. Same reason for michael@0: // EnableRunnable and SetFrequencyRunnable. michael@0: NS_DispatchToMainThread(new DisableRunnable()); michael@0: } michael@0: michael@0: void michael@0: FMRadioService::SetFrequency(double aFrequencyInMHz, michael@0: FMRadioReplyRunnable* aReplyRunnable) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); michael@0: MOZ_ASSERT(aReplyRunnable); michael@0: michael@0: switch (mState) { michael@0: case Disabled: michael@0: aReplyRunnable->SetReply( michael@0: ErrorResponse(NS_LITERAL_STRING("FM radio currently disabled"))); michael@0: NS_DispatchToMainThread(aReplyRunnable); michael@0: return; michael@0: case Enabling: michael@0: aReplyRunnable->SetReply( michael@0: ErrorResponse(NS_LITERAL_STRING("FM radio currently enabling"))); michael@0: NS_DispatchToMainThread(aReplyRunnable); michael@0: return; michael@0: case Disabling: michael@0: aReplyRunnable->SetReply( michael@0: ErrorResponse(NS_LITERAL_STRING("FM radio currently disabling"))); michael@0: NS_DispatchToMainThread(aReplyRunnable); michael@0: return; michael@0: case Seeking: michael@0: CancelFMRadioSeek(); michael@0: TransitionState(ErrorResponse( michael@0: NS_LITERAL_STRING("Seek action is cancelled")), Enabled); michael@0: break; michael@0: case Enabled: michael@0: break; michael@0: } michael@0: michael@0: int32_t roundedFrequency = RoundFrequency(aFrequencyInMHz); michael@0: michael@0: if (!roundedFrequency) { michael@0: aReplyRunnable->SetReply(ErrorResponse( michael@0: NS_LITERAL_STRING("Frequency is out of range"))); michael@0: NS_DispatchToMainThread(aReplyRunnable); michael@0: return; michael@0: } michael@0: michael@0: NS_DispatchToMainThread(new SetFrequencyRunnable(roundedFrequency)); michael@0: michael@0: aReplyRunnable->SetReply(SuccessResponse()); michael@0: NS_DispatchToMainThread(aReplyRunnable); michael@0: } michael@0: michael@0: void michael@0: FMRadioService::Seek(FMRadioSeekDirection aDirection, michael@0: FMRadioReplyRunnable* aReplyRunnable) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); michael@0: MOZ_ASSERT(aReplyRunnable); michael@0: michael@0: switch (mState) { michael@0: case Enabling: michael@0: aReplyRunnable->SetReply( michael@0: ErrorResponse(NS_LITERAL_STRING("FM radio currently enabling"))); michael@0: NS_DispatchToMainThread(aReplyRunnable); michael@0: return; michael@0: case Disabled: michael@0: aReplyRunnable->SetReply( michael@0: ErrorResponse(NS_LITERAL_STRING("FM radio currently disabled"))); michael@0: NS_DispatchToMainThread(aReplyRunnable); michael@0: return; michael@0: case Seeking: michael@0: aReplyRunnable->SetReply( michael@0: ErrorResponse(NS_LITERAL_STRING("FM radio currently seeking"))); michael@0: NS_DispatchToMainThread(aReplyRunnable); michael@0: return; michael@0: case Disabling: michael@0: aReplyRunnable->SetReply( michael@0: ErrorResponse(NS_LITERAL_STRING("FM radio currently disabling"))); michael@0: NS_DispatchToMainThread(aReplyRunnable); michael@0: return; michael@0: case Enabled: michael@0: break; michael@0: } michael@0: michael@0: SetState(Seeking); michael@0: mPendingRequest = aReplyRunnable; michael@0: michael@0: NS_DispatchToMainThread(new SeekRunnable(aDirection)); michael@0: } michael@0: michael@0: void michael@0: FMRadioService::CancelSeek(FMRadioReplyRunnable* aReplyRunnable) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); michael@0: MOZ_ASSERT(aReplyRunnable); michael@0: michael@0: // We accept canceling seek request only if it's currently seeking. michael@0: if (mState != Seeking) { michael@0: aReplyRunnable->SetReply( michael@0: ErrorResponse(NS_LITERAL_STRING("FM radio currently not seeking"))); michael@0: NS_DispatchToMainThread(aReplyRunnable); michael@0: return; michael@0: } michael@0: michael@0: // Cancel the seek immediately to prevent it from completing. michael@0: CancelFMRadioSeek(); michael@0: michael@0: TransitionState( michael@0: ErrorResponse(NS_LITERAL_STRING("Seek action is cancelled")), Enabled); michael@0: michael@0: aReplyRunnable->SetReply(SuccessResponse()); michael@0: NS_DispatchToMainThread(aReplyRunnable); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: FMRadioService::Observe(nsISupports * aSubject, michael@0: const char * aTopic, michael@0: const char16_t * aData) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(sFMRadioService); michael@0: michael@0: if (strcmp(aTopic, MOZSETTINGS_CHANGED_ID) != 0) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // The string that we're interested in will be a JSON string looks like: michael@0: // {"key":"airplaneMode.enabled","value":true} michael@0: AutoSafeJSContext cx; michael@0: const nsDependentString dataStr(aData); michael@0: JS::Rooted val(cx); michael@0: if (!JS_ParseJSON(cx, dataStr.get(), dataStr.Length(), &val) || michael@0: !val.isObject()) { michael@0: NS_WARNING("Bad JSON string format."); michael@0: return NS_OK; michael@0: } michael@0: michael@0: JS::Rooted obj(cx, &val.toObject()); michael@0: JS::Rooted key(cx); michael@0: if (!JS_GetProperty(cx, obj, "key", &key) || michael@0: !key.isString()) { michael@0: NS_WARNING("Failed to get string property `key`."); michael@0: return NS_OK; michael@0: } michael@0: michael@0: JS::Rooted jsKey(cx, key.toString()); michael@0: nsDependentJSString keyStr; michael@0: if (!keyStr.init(cx, jsKey)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (keyStr.EqualsLiteral(SETTING_KEY_AIRPLANEMODE_ENABLED)) { michael@0: JS::Rooted value(cx); michael@0: if (!JS_GetProperty(cx, obj, "value", &value)) { michael@0: NS_WARNING("Failed to get property `value`."); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!value.isBoolean()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: mAirplaneModeEnabled = value.toBoolean(); michael@0: mHasReadAirplaneModeSetting = true; michael@0: michael@0: // Disable the FM radio HW if Airplane mode is enabled. michael@0: if (mAirplaneModeEnabled) { michael@0: Disable(nullptr); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: FMRadioService::NotifyFMRadioEvent(FMRadioEventType aType) michael@0: { michael@0: mObserverList.Broadcast(aType); michael@0: } michael@0: michael@0: void michael@0: FMRadioService::Notify(const FMRadioOperationInformation& aInfo) michael@0: { michael@0: switch (aInfo.operation()) { michael@0: case FM_RADIO_OPERATION_ENABLE: michael@0: MOZ_ASSERT(IsFMRadioOn()); michael@0: MOZ_ASSERT(mState == Disabling || mState == Enabling); michael@0: michael@0: // If we're disabling, disable the radio right now. michael@0: if (mState == Disabling) { michael@0: DoDisable(); michael@0: return; michael@0: } michael@0: michael@0: // Fire success callback on the enable request. michael@0: TransitionState(SuccessResponse(), Enabled); michael@0: michael@0: // To make sure the FM app will get the right frequency after the FM michael@0: // radio is enabled, we have to set the frequency first. michael@0: SetFMRadioFrequency(mPendingFrequencyInKHz); michael@0: michael@0: // Bug 949855: enable audio after the FM radio HW is enabled, to make sure michael@0: // 'hw.fm.isAnalog' could be detected as |true| during first time launch. michael@0: // This case is for audio output on analog path, i.e. 'ro.moz.fm.noAnalog' michael@0: // is not |true|. michael@0: EnableAudio(true); michael@0: michael@0: // Update the current frequency without sending the`FrequencyChanged` michael@0: // event, to make sure the FM app will get the right frequency when the michael@0: // `EnabledChange` event is sent. michael@0: mPendingFrequencyInKHz = GetFMRadioFrequency(); michael@0: UpdatePowerState(); michael@0: michael@0: // The frequency was changed from '0' to some meaningful number, so we michael@0: // should send the `FrequencyChanged` event manually. michael@0: NotifyFMRadioEvent(FrequencyChanged); michael@0: break; michael@0: case FM_RADIO_OPERATION_DISABLE: michael@0: MOZ_ASSERT(mState == Disabling); michael@0: michael@0: TransitionState(SuccessResponse(), Disabled); michael@0: UpdatePowerState(); michael@0: break; michael@0: case FM_RADIO_OPERATION_SEEK: michael@0: michael@0: // Seek action might be cancelled by SetFrequency(), we need to check if michael@0: // the current state is Seeking. michael@0: if (mState == Seeking) { michael@0: TransitionState(SuccessResponse(), Enabled); michael@0: } michael@0: michael@0: UpdateFrequency(); michael@0: break; michael@0: case FM_RADIO_OPERATION_TUNE: michael@0: UpdateFrequency(); michael@0: break; michael@0: default: michael@0: MOZ_CRASH(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: FMRadioService::UpdatePowerState() michael@0: { michael@0: bool enabled = IsFMRadioOn(); michael@0: if (enabled != mEnabled) { michael@0: mEnabled = enabled; michael@0: NotifyFMRadioEvent(EnabledChanged); michael@0: } michael@0: } michael@0: michael@0: void michael@0: FMRadioService::UpdateFrequency() michael@0: { michael@0: int32_t frequency = GetFMRadioFrequency(); michael@0: if (mPendingFrequencyInKHz != frequency) { michael@0: mPendingFrequencyInKHz = frequency; michael@0: NotifyFMRadioEvent(FrequencyChanged); michael@0: } michael@0: } michael@0: michael@0: // static michael@0: FMRadioService* michael@0: FMRadioService::Singleton() michael@0: { michael@0: MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: if (!sFMRadioService) { michael@0: sFMRadioService = new FMRadioService(); michael@0: } michael@0: michael@0: return sFMRadioService; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(FMRadioService, nsIObserver) michael@0: michael@0: END_FMRADIO_NAMESPACE michael@0: