1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/fmradio/FMRadioService.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,816 @@ 1.4 +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ 1.5 +/* vim: set ts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.8 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "FMRadioService.h" 1.11 +#include "mozilla/Hal.h" 1.12 +#include "nsIAudioManager.h" 1.13 +#include "AudioManager.h" 1.14 +#include "nsDOMClassInfo.h" 1.15 +#include "mozilla/Preferences.h" 1.16 +#include "mozilla/dom/FMRadioChild.h" 1.17 +#include "nsIObserverService.h" 1.18 +#include "nsISettingsService.h" 1.19 +#include "nsJSUtils.h" 1.20 +#include "nsCxPusher.h" 1.21 + 1.22 +#define BAND_87500_108000_kHz 1 1.23 +#define BAND_76000_108000_kHz 2 1.24 +#define BAND_76000_90000_kHz 3 1.25 + 1.26 +#define CHANNEL_WIDTH_200KHZ 200 1.27 +#define CHANNEL_WIDTH_100KHZ 100 1.28 +#define CHANNEL_WIDTH_50KHZ 50 1.29 + 1.30 +#define MOZSETTINGS_CHANGED_ID "mozsettings-changed" 1.31 +#define SETTING_KEY_AIRPLANEMODE_ENABLED "airplaneMode.enabled" 1.32 + 1.33 +using namespace mozilla::hal; 1.34 +using mozilla::Preferences; 1.35 + 1.36 +BEGIN_FMRADIO_NAMESPACE 1.37 + 1.38 +// static 1.39 +IFMRadioService* 1.40 +IFMRadioService::Singleton() 1.41 +{ 1.42 + if (XRE_GetProcessType() != GeckoProcessType_Default) { 1.43 + return FMRadioChild::Singleton(); 1.44 + } else { 1.45 + return FMRadioService::Singleton(); 1.46 + } 1.47 +} 1.48 + 1.49 +StaticRefPtr<FMRadioService> FMRadioService::sFMRadioService; 1.50 + 1.51 +FMRadioService::FMRadioService() 1.52 + : mPendingFrequencyInKHz(0) 1.53 + , mState(Disabled) 1.54 + , mHasReadAirplaneModeSetting(false) 1.55 + , mAirplaneModeEnabled(false) 1.56 + , mPendingRequest(nullptr) 1.57 + , mObserverList(FMRadioEventObserverList()) 1.58 +{ 1.59 + 1.60 + // Read power state and frequency from Hal. 1.61 + mEnabled = IsFMRadioOn(); 1.62 + if (mEnabled) { 1.63 + mPendingFrequencyInKHz = GetFMRadioFrequency(); 1.64 + SetState(Enabled); 1.65 + } 1.66 + 1.67 + switch (Preferences::GetInt("dom.fmradio.band", BAND_87500_108000_kHz)) { 1.68 + case BAND_76000_90000_kHz: 1.69 + mUpperBoundInKHz = 90000; 1.70 + mLowerBoundInKHz = 76000; 1.71 + break; 1.72 + case BAND_76000_108000_kHz: 1.73 + mUpperBoundInKHz = 108000; 1.74 + mLowerBoundInKHz = 76000; 1.75 + break; 1.76 + case BAND_87500_108000_kHz: 1.77 + default: 1.78 + mUpperBoundInKHz = 108000; 1.79 + mLowerBoundInKHz = 87500; 1.80 + break; 1.81 + } 1.82 + 1.83 + switch (Preferences::GetInt("dom.fmradio.channelWidth", 1.84 + CHANNEL_WIDTH_100KHZ)) { 1.85 + case CHANNEL_WIDTH_200KHZ: 1.86 + mChannelWidthInKHz = 200; 1.87 + break; 1.88 + case CHANNEL_WIDTH_50KHZ: 1.89 + mChannelWidthInKHz = 50; 1.90 + break; 1.91 + case CHANNEL_WIDTH_100KHZ: 1.92 + default: 1.93 + mChannelWidthInKHz = 100; 1.94 + break; 1.95 + } 1.96 + 1.97 + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 1.98 + 1.99 + if (obs && NS_FAILED(obs->AddObserver(this, 1.100 + MOZSETTINGS_CHANGED_ID, 1.101 + /* useWeak */ false))) { 1.102 + NS_WARNING("Failed to add settings change observer!"); 1.103 + } 1.104 + 1.105 + RegisterFMRadioObserver(this); 1.106 +} 1.107 + 1.108 +FMRadioService::~FMRadioService() 1.109 +{ 1.110 + UnregisterFMRadioObserver(this); 1.111 +} 1.112 + 1.113 +class EnableRunnable MOZ_FINAL : public nsRunnable 1.114 +{ 1.115 +public: 1.116 + EnableRunnable(int32_t aUpperLimit, int32_t aLowerLimit, int32_t aSpaceType) 1.117 + : mUpperLimit(aUpperLimit) 1.118 + , mLowerLimit(aLowerLimit) 1.119 + , mSpaceType(aSpaceType) { } 1.120 + 1.121 + NS_IMETHOD Run() 1.122 + { 1.123 + FMRadioSettings info; 1.124 + info.upperLimit() = mUpperLimit; 1.125 + info.lowerLimit() = mLowerLimit; 1.126 + info.spaceType() = mSpaceType; 1.127 + 1.128 + EnableFMRadio(info); 1.129 + 1.130 + return NS_OK; 1.131 + } 1.132 + 1.133 +private: 1.134 + int32_t mUpperLimit; 1.135 + int32_t mLowerLimit; 1.136 + int32_t mSpaceType; 1.137 +}; 1.138 + 1.139 +/** 1.140 + * Read the airplane-mode setting, if the airplane-mode is not enabled, we 1.141 + * enable the FM radio. 1.142 + */ 1.143 +class ReadAirplaneModeSettingTask MOZ_FINAL : public nsISettingsServiceCallback 1.144 +{ 1.145 +public: 1.146 + NS_DECL_ISUPPORTS 1.147 + 1.148 + ReadAirplaneModeSettingTask(nsRefPtr<FMRadioReplyRunnable> aPendingRequest) 1.149 + : mPendingRequest(aPendingRequest) { } 1.150 + 1.151 + NS_IMETHOD 1.152 + Handle(const nsAString& aName, JS::Handle<JS::Value> aResult) 1.153 + { 1.154 + FMRadioService* fmRadioService = FMRadioService::Singleton(); 1.155 + MOZ_ASSERT(mPendingRequest == fmRadioService->mPendingRequest); 1.156 + 1.157 + fmRadioService->mHasReadAirplaneModeSetting = true; 1.158 + 1.159 + if (!aResult.isBoolean()) { 1.160 + // Failed to read the setting value, set the state back to Disabled. 1.161 + fmRadioService->TransitionState( 1.162 + ErrorResponse(NS_LITERAL_STRING("Unexpected error")), Disabled); 1.163 + return NS_OK; 1.164 + } 1.165 + 1.166 + fmRadioService->mAirplaneModeEnabled = aResult.toBoolean(); 1.167 + if (!fmRadioService->mAirplaneModeEnabled) { 1.168 + EnableRunnable* runnable = 1.169 + new EnableRunnable(fmRadioService->mUpperBoundInKHz, 1.170 + fmRadioService->mLowerBoundInKHz, 1.171 + fmRadioService->mChannelWidthInKHz); 1.172 + NS_DispatchToMainThread(runnable); 1.173 + } else { 1.174 + // Airplane mode is enabled, set the state back to Disabled. 1.175 + fmRadioService->TransitionState(ErrorResponse( 1.176 + NS_LITERAL_STRING("Airplane mode currently enabled")), Disabled); 1.177 + } 1.178 + 1.179 + return NS_OK; 1.180 + } 1.181 + 1.182 + NS_IMETHOD 1.183 + HandleError(const nsAString& aName) 1.184 + { 1.185 + FMRadioService* fmRadioService = FMRadioService::Singleton(); 1.186 + MOZ_ASSERT(mPendingRequest == fmRadioService->mPendingRequest); 1.187 + 1.188 + fmRadioService->TransitionState(ErrorResponse( 1.189 + NS_LITERAL_STRING("Unexpected error")), Disabled); 1.190 + 1.191 + return NS_OK; 1.192 + } 1.193 + 1.194 +private: 1.195 + nsRefPtr<FMRadioReplyRunnable> mPendingRequest; 1.196 +}; 1.197 + 1.198 +NS_IMPL_ISUPPORTS(ReadAirplaneModeSettingTask, nsISettingsServiceCallback) 1.199 + 1.200 +class DisableRunnable MOZ_FINAL : public nsRunnable 1.201 +{ 1.202 +public: 1.203 + DisableRunnable() { } 1.204 + 1.205 + NS_IMETHOD Run() 1.206 + { 1.207 + // Fix Bug 796733. DisableFMRadio should be called before 1.208 + // SetFmRadioAudioEnabled to prevent the annoying beep sound. 1.209 + DisableFMRadio(); 1.210 + IFMRadioService::Singleton()->EnableAudio(false); 1.211 + 1.212 + return NS_OK; 1.213 + } 1.214 +}; 1.215 + 1.216 +class SetFrequencyRunnable MOZ_FINAL : public nsRunnable 1.217 +{ 1.218 +public: 1.219 + SetFrequencyRunnable(int32_t aFrequency) 1.220 + : mFrequency(aFrequency) { } 1.221 + 1.222 + NS_IMETHOD Run() 1.223 + { 1.224 + SetFMRadioFrequency(mFrequency); 1.225 + return NS_OK; 1.226 + } 1.227 + 1.228 +private: 1.229 + int32_t mFrequency; 1.230 +}; 1.231 + 1.232 +class SeekRunnable MOZ_FINAL : public nsRunnable 1.233 +{ 1.234 +public: 1.235 + SeekRunnable(FMRadioSeekDirection aDirection) : mDirection(aDirection) { } 1.236 + 1.237 + NS_IMETHOD Run() 1.238 + { 1.239 + switch (mDirection) { 1.240 + case FM_RADIO_SEEK_DIRECTION_UP: 1.241 + case FM_RADIO_SEEK_DIRECTION_DOWN: 1.242 + FMRadioSeek(mDirection); 1.243 + break; 1.244 + default: 1.245 + MOZ_CRASH(); 1.246 + } 1.247 + 1.248 + return NS_OK; 1.249 + } 1.250 + 1.251 +private: 1.252 + FMRadioSeekDirection mDirection; 1.253 +}; 1.254 + 1.255 +void 1.256 +FMRadioService::TransitionState(const FMRadioResponseType& aResponse, 1.257 + FMRadioState aState) 1.258 +{ 1.259 + if (mPendingRequest) { 1.260 + mPendingRequest->SetReply(aResponse); 1.261 + NS_DispatchToMainThread(mPendingRequest); 1.262 + } 1.263 + 1.264 + SetState(aState); 1.265 +} 1.266 + 1.267 +void 1.268 +FMRadioService::SetState(FMRadioState aState) 1.269 +{ 1.270 + mState = aState; 1.271 + mPendingRequest = nullptr; 1.272 +} 1.273 + 1.274 +void 1.275 +FMRadioService::AddObserver(FMRadioEventObserver* aObserver) 1.276 +{ 1.277 + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); 1.278 + mObserverList.AddObserver(aObserver); 1.279 +} 1.280 + 1.281 +void 1.282 +FMRadioService::RemoveObserver(FMRadioEventObserver* aObserver) 1.283 +{ 1.284 + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); 1.285 + mObserverList.RemoveObserver(aObserver); 1.286 + 1.287 + if (mObserverList.Length() == 0) 1.288 + { 1.289 + // Turning off the FM radio HW because observer list is empty. 1.290 + if (IsFMRadioOn()) { 1.291 + NS_DispatchToMainThread(new DisableRunnable()); 1.292 + } 1.293 + } 1.294 +} 1.295 + 1.296 +void 1.297 +FMRadioService::EnableAudio(bool aAudioEnabled) 1.298 +{ 1.299 + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); 1.300 + 1.301 + nsCOMPtr<nsIAudioManager> audioManager = 1.302 + do_GetService("@mozilla.org/telephony/audiomanager;1"); 1.303 + if (!audioManager) { 1.304 + return; 1.305 + } 1.306 + 1.307 + bool audioEnabled; 1.308 + audioManager->GetFmRadioAudioEnabled(&audioEnabled); 1.309 + if (audioEnabled != aAudioEnabled) { 1.310 + audioManager->SetFmRadioAudioEnabled(aAudioEnabled); 1.311 + } 1.312 +} 1.313 + 1.314 +/** 1.315 + * Round the frequency to match the range of frequency and the channel width. If 1.316 + * the given frequency is out of range, return 0. For example: 1.317 + * - lower: 87500KHz, upper: 108000KHz, channel width: 200KHz 1.318 + * 87.6MHz is rounded to 87700KHz 1.319 + * 87.58MHz is rounded to 87500KHz 1.320 + * 87.49MHz is rounded to 87500KHz 1.321 + * 109MHz is not rounded, 0 will be returned 1.322 + * 1.323 + * We take frequency in MHz to prevent precision losing, and return rounded 1.324 + * value in KHz for Gonk using. 1.325 + */ 1.326 +int32_t 1.327 +FMRadioService::RoundFrequency(double aFrequencyInMHz) 1.328 +{ 1.329 + double halfChannelWidthInMHz = mChannelWidthInKHz / 1000.0 / 2; 1.330 + 1.331 + // Make sure 87.49999MHz would be rounded to the lower bound when 1.332 + // the lower bound is 87500KHz. 1.333 + if (aFrequencyInMHz < mLowerBoundInKHz / 1000.0 - halfChannelWidthInMHz || 1.334 + aFrequencyInMHz > mUpperBoundInKHz / 1000.0 + halfChannelWidthInMHz) { 1.335 + return 0; 1.336 + } 1.337 + 1.338 + int32_t partToBeRounded = round(aFrequencyInMHz * 1000) - mLowerBoundInKHz; 1.339 + int32_t roundedPart = round(partToBeRounded / (double)mChannelWidthInKHz) * 1.340 + mChannelWidthInKHz; 1.341 + 1.342 + return mLowerBoundInKHz + roundedPart; 1.343 +} 1.344 + 1.345 +bool 1.346 +FMRadioService::IsEnabled() const 1.347 +{ 1.348 + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); 1.349 + return IsFMRadioOn(); 1.350 +} 1.351 + 1.352 +double 1.353 +FMRadioService::GetFrequency() const 1.354 +{ 1.355 + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); 1.356 + if (IsEnabled()) { 1.357 + int32_t frequencyInKHz = GetFMRadioFrequency(); 1.358 + return frequencyInKHz / 1000.0; 1.359 + } 1.360 + 1.361 + return 0; 1.362 +} 1.363 + 1.364 +double 1.365 +FMRadioService::GetFrequencyUpperBound() const 1.366 +{ 1.367 + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); 1.368 + return mUpperBoundInKHz / 1000.0; 1.369 +} 1.370 + 1.371 +double 1.372 +FMRadioService::GetFrequencyLowerBound() const 1.373 +{ 1.374 + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); 1.375 + return mLowerBoundInKHz / 1000.0; 1.376 +} 1.377 + 1.378 +double 1.379 +FMRadioService::GetChannelWidth() const 1.380 +{ 1.381 + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); 1.382 + return mChannelWidthInKHz / 1000.0; 1.383 +} 1.384 + 1.385 +void 1.386 +FMRadioService::Enable(double aFrequencyInMHz, 1.387 + FMRadioReplyRunnable* aReplyRunnable) 1.388 +{ 1.389 + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); 1.390 + MOZ_ASSERT(aReplyRunnable); 1.391 + 1.392 + switch (mState) { 1.393 + case Seeking: 1.394 + case Enabled: 1.395 + aReplyRunnable->SetReply( 1.396 + ErrorResponse(NS_LITERAL_STRING("FM radio currently enabled"))); 1.397 + NS_DispatchToMainThread(aReplyRunnable); 1.398 + return; 1.399 + case Disabling: 1.400 + aReplyRunnable->SetReply( 1.401 + ErrorResponse(NS_LITERAL_STRING("FM radio currently disabling"))); 1.402 + NS_DispatchToMainThread(aReplyRunnable); 1.403 + return; 1.404 + case Enabling: 1.405 + aReplyRunnable->SetReply( 1.406 + ErrorResponse(NS_LITERAL_STRING("FM radio currently enabling"))); 1.407 + NS_DispatchToMainThread(aReplyRunnable); 1.408 + return; 1.409 + case Disabled: 1.410 + break; 1.411 + } 1.412 + 1.413 + int32_t roundedFrequency = RoundFrequency(aFrequencyInMHz); 1.414 + 1.415 + if (!roundedFrequency) { 1.416 + aReplyRunnable->SetReply(ErrorResponse( 1.417 + NS_LITERAL_STRING("Frequency is out of range"))); 1.418 + NS_DispatchToMainThread(aReplyRunnable); 1.419 + return; 1.420 + } 1.421 + 1.422 + if (mHasReadAirplaneModeSetting && mAirplaneModeEnabled) { 1.423 + aReplyRunnable->SetReply(ErrorResponse( 1.424 + NS_LITERAL_STRING("Airplane mode currently enabled"))); 1.425 + NS_DispatchToMainThread(aReplyRunnable); 1.426 + return; 1.427 + } 1.428 + 1.429 + SetState(Enabling); 1.430 + // Cache the enable request just in case disable() is called 1.431 + // while the FM radio HW is being enabled. 1.432 + mPendingRequest = aReplyRunnable; 1.433 + 1.434 + // Cache the frequency value, and set it after the FM radio HW is enabled 1.435 + mPendingFrequencyInKHz = roundedFrequency; 1.436 + 1.437 + if (!mHasReadAirplaneModeSetting) { 1.438 + nsCOMPtr<nsISettingsService> settings = 1.439 + do_GetService("@mozilla.org/settingsService;1"); 1.440 + 1.441 + nsCOMPtr<nsISettingsServiceLock> settingsLock; 1.442 + nsresult rv = settings->CreateLock(nullptr, getter_AddRefs(settingsLock)); 1.443 + if (NS_FAILED(rv)) { 1.444 + TransitionState(ErrorResponse( 1.445 + NS_LITERAL_STRING("Can't create settings lock")), Disabled); 1.446 + return; 1.447 + } 1.448 + 1.449 + nsRefPtr<ReadAirplaneModeSettingTask> callback = 1.450 + new ReadAirplaneModeSettingTask(mPendingRequest); 1.451 + 1.452 + rv = settingsLock->Get(SETTING_KEY_AIRPLANEMODE_ENABLED, callback); 1.453 + if (NS_FAILED(rv)) { 1.454 + TransitionState(ErrorResponse( 1.455 + NS_LITERAL_STRING("Can't get settings lock")), Disabled); 1.456 + } 1.457 + 1.458 + return; 1.459 + } 1.460 + 1.461 + NS_DispatchToMainThread(new EnableRunnable(mUpperBoundInKHz, 1.462 + mLowerBoundInKHz, 1.463 + mChannelWidthInKHz)); 1.464 +} 1.465 + 1.466 +void 1.467 +FMRadioService::Disable(FMRadioReplyRunnable* aReplyRunnable) 1.468 +{ 1.469 + // When airplane-mode is enabled, we will call this function from 1.470 + // FMRadioService::Observe without passing a FMRadioReplyRunnable, 1.471 + // so we have to check if |aReplyRunnable| is null before we dispatch it. 1.472 + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); 1.473 + 1.474 + switch (mState) { 1.475 + case Disabling: 1.476 + if (aReplyRunnable) { 1.477 + aReplyRunnable->SetReply( 1.478 + ErrorResponse(NS_LITERAL_STRING("FM radio currently disabling"))); 1.479 + NS_DispatchToMainThread(aReplyRunnable); 1.480 + } 1.481 + return; 1.482 + case Disabled: 1.483 + if (aReplyRunnable) { 1.484 + aReplyRunnable->SetReply( 1.485 + ErrorResponse(NS_LITERAL_STRING("FM radio currently disabled"))); 1.486 + NS_DispatchToMainThread(aReplyRunnable); 1.487 + } 1.488 + return; 1.489 + case Enabled: 1.490 + case Enabling: 1.491 + case Seeking: 1.492 + break; 1.493 + } 1.494 + 1.495 + nsRefPtr<FMRadioReplyRunnable> enablingRequest = mPendingRequest; 1.496 + 1.497 + // If the FM Radio is currently seeking, no fail-to-seek or similar 1.498 + // event will be fired, execute the seek callback manually. 1.499 + if (mState == Seeking) { 1.500 + TransitionState(ErrorResponse( 1.501 + NS_LITERAL_STRING("Seek action is cancelled")), Disabling); 1.502 + } 1.503 + 1.504 + FMRadioState preState = mState; 1.505 + SetState(Disabling); 1.506 + mPendingRequest = aReplyRunnable; 1.507 + 1.508 + if (preState == Enabling) { 1.509 + // If the radio is currently enabling, we fire the error callback on the 1.510 + // enable request immediately. When the radio finishes enabling, we'll call 1.511 + // DoDisable and fire the success callback on the disable request. 1.512 + enablingRequest->SetReply( 1.513 + ErrorResponse(NS_LITERAL_STRING("Enable action is cancelled"))); 1.514 + NS_DispatchToMainThread(enablingRequest); 1.515 + 1.516 + // If we haven't read the airplane mode settings yet we won't enable the 1.517 + // FM radio HW, so fail the disable request immediately. 1.518 + if (!mHasReadAirplaneModeSetting) { 1.519 + SetState(Disabled); 1.520 + 1.521 + if (aReplyRunnable) { 1.522 + aReplyRunnable->SetReply(SuccessResponse()); 1.523 + NS_DispatchToMainThread(aReplyRunnable); 1.524 + } 1.525 + } 1.526 + 1.527 + return; 1.528 + } 1.529 + 1.530 + DoDisable(); 1.531 +} 1.532 + 1.533 +void 1.534 +FMRadioService::DoDisable() 1.535 +{ 1.536 + // To make such codes work: 1.537 + // navigator.mozFMRadio.disable(); 1.538 + // navigator.mozFMRadio.ondisabled = function() { 1.539 + // console.log("We will catch disabled event "); 1.540 + // }; 1.541 + // we need to call hal::DisableFMRadio() asynchronously. Same reason for 1.542 + // EnableRunnable and SetFrequencyRunnable. 1.543 + NS_DispatchToMainThread(new DisableRunnable()); 1.544 +} 1.545 + 1.546 +void 1.547 +FMRadioService::SetFrequency(double aFrequencyInMHz, 1.548 + FMRadioReplyRunnable* aReplyRunnable) 1.549 +{ 1.550 + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); 1.551 + MOZ_ASSERT(aReplyRunnable); 1.552 + 1.553 + switch (mState) { 1.554 + case Disabled: 1.555 + aReplyRunnable->SetReply( 1.556 + ErrorResponse(NS_LITERAL_STRING("FM radio currently disabled"))); 1.557 + NS_DispatchToMainThread(aReplyRunnable); 1.558 + return; 1.559 + case Enabling: 1.560 + aReplyRunnable->SetReply( 1.561 + ErrorResponse(NS_LITERAL_STRING("FM radio currently enabling"))); 1.562 + NS_DispatchToMainThread(aReplyRunnable); 1.563 + return; 1.564 + case Disabling: 1.565 + aReplyRunnable->SetReply( 1.566 + ErrorResponse(NS_LITERAL_STRING("FM radio currently disabling"))); 1.567 + NS_DispatchToMainThread(aReplyRunnable); 1.568 + return; 1.569 + case Seeking: 1.570 + CancelFMRadioSeek(); 1.571 + TransitionState(ErrorResponse( 1.572 + NS_LITERAL_STRING("Seek action is cancelled")), Enabled); 1.573 + break; 1.574 + case Enabled: 1.575 + break; 1.576 + } 1.577 + 1.578 + int32_t roundedFrequency = RoundFrequency(aFrequencyInMHz); 1.579 + 1.580 + if (!roundedFrequency) { 1.581 + aReplyRunnable->SetReply(ErrorResponse( 1.582 + NS_LITERAL_STRING("Frequency is out of range"))); 1.583 + NS_DispatchToMainThread(aReplyRunnable); 1.584 + return; 1.585 + } 1.586 + 1.587 + NS_DispatchToMainThread(new SetFrequencyRunnable(roundedFrequency)); 1.588 + 1.589 + aReplyRunnable->SetReply(SuccessResponse()); 1.590 + NS_DispatchToMainThread(aReplyRunnable); 1.591 +} 1.592 + 1.593 +void 1.594 +FMRadioService::Seek(FMRadioSeekDirection aDirection, 1.595 + FMRadioReplyRunnable* aReplyRunnable) 1.596 +{ 1.597 + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); 1.598 + MOZ_ASSERT(aReplyRunnable); 1.599 + 1.600 + switch (mState) { 1.601 + case Enabling: 1.602 + aReplyRunnable->SetReply( 1.603 + ErrorResponse(NS_LITERAL_STRING("FM radio currently enabling"))); 1.604 + NS_DispatchToMainThread(aReplyRunnable); 1.605 + return; 1.606 + case Disabled: 1.607 + aReplyRunnable->SetReply( 1.608 + ErrorResponse(NS_LITERAL_STRING("FM radio currently disabled"))); 1.609 + NS_DispatchToMainThread(aReplyRunnable); 1.610 + return; 1.611 + case Seeking: 1.612 + aReplyRunnable->SetReply( 1.613 + ErrorResponse(NS_LITERAL_STRING("FM radio currently seeking"))); 1.614 + NS_DispatchToMainThread(aReplyRunnable); 1.615 + return; 1.616 + case Disabling: 1.617 + aReplyRunnable->SetReply( 1.618 + ErrorResponse(NS_LITERAL_STRING("FM radio currently disabling"))); 1.619 + NS_DispatchToMainThread(aReplyRunnable); 1.620 + return; 1.621 + case Enabled: 1.622 + break; 1.623 + } 1.624 + 1.625 + SetState(Seeking); 1.626 + mPendingRequest = aReplyRunnable; 1.627 + 1.628 + NS_DispatchToMainThread(new SeekRunnable(aDirection)); 1.629 +} 1.630 + 1.631 +void 1.632 +FMRadioService::CancelSeek(FMRadioReplyRunnable* aReplyRunnable) 1.633 +{ 1.634 + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); 1.635 + MOZ_ASSERT(aReplyRunnable); 1.636 + 1.637 + // We accept canceling seek request only if it's currently seeking. 1.638 + if (mState != Seeking) { 1.639 + aReplyRunnable->SetReply( 1.640 + ErrorResponse(NS_LITERAL_STRING("FM radio currently not seeking"))); 1.641 + NS_DispatchToMainThread(aReplyRunnable); 1.642 + return; 1.643 + } 1.644 + 1.645 + // Cancel the seek immediately to prevent it from completing. 1.646 + CancelFMRadioSeek(); 1.647 + 1.648 + TransitionState( 1.649 + ErrorResponse(NS_LITERAL_STRING("Seek action is cancelled")), Enabled); 1.650 + 1.651 + aReplyRunnable->SetReply(SuccessResponse()); 1.652 + NS_DispatchToMainThread(aReplyRunnable); 1.653 +} 1.654 + 1.655 +NS_IMETHODIMP 1.656 +FMRadioService::Observe(nsISupports * aSubject, 1.657 + const char * aTopic, 1.658 + const char16_t * aData) 1.659 +{ 1.660 + MOZ_ASSERT(NS_IsMainThread()); 1.661 + MOZ_ASSERT(sFMRadioService); 1.662 + 1.663 + if (strcmp(aTopic, MOZSETTINGS_CHANGED_ID) != 0) { 1.664 + return NS_OK; 1.665 + } 1.666 + 1.667 + // The string that we're interested in will be a JSON string looks like: 1.668 + // {"key":"airplaneMode.enabled","value":true} 1.669 + AutoSafeJSContext cx; 1.670 + const nsDependentString dataStr(aData); 1.671 + JS::Rooted<JS::Value> val(cx); 1.672 + if (!JS_ParseJSON(cx, dataStr.get(), dataStr.Length(), &val) || 1.673 + !val.isObject()) { 1.674 + NS_WARNING("Bad JSON string format."); 1.675 + return NS_OK; 1.676 + } 1.677 + 1.678 + JS::Rooted<JSObject*> obj(cx, &val.toObject()); 1.679 + JS::Rooted<JS::Value> key(cx); 1.680 + if (!JS_GetProperty(cx, obj, "key", &key) || 1.681 + !key.isString()) { 1.682 + NS_WARNING("Failed to get string property `key`."); 1.683 + return NS_OK; 1.684 + } 1.685 + 1.686 + JS::Rooted<JSString*> jsKey(cx, key.toString()); 1.687 + nsDependentJSString keyStr; 1.688 + if (!keyStr.init(cx, jsKey)) { 1.689 + return NS_OK; 1.690 + } 1.691 + 1.692 + if (keyStr.EqualsLiteral(SETTING_KEY_AIRPLANEMODE_ENABLED)) { 1.693 + JS::Rooted<JS::Value> value(cx); 1.694 + if (!JS_GetProperty(cx, obj, "value", &value)) { 1.695 + NS_WARNING("Failed to get property `value`."); 1.696 + return NS_OK; 1.697 + } 1.698 + 1.699 + if (!value.isBoolean()) { 1.700 + return NS_OK; 1.701 + } 1.702 + 1.703 + mAirplaneModeEnabled = value.toBoolean(); 1.704 + mHasReadAirplaneModeSetting = true; 1.705 + 1.706 + // Disable the FM radio HW if Airplane mode is enabled. 1.707 + if (mAirplaneModeEnabled) { 1.708 + Disable(nullptr); 1.709 + } 1.710 + } 1.711 + 1.712 + return NS_OK; 1.713 +} 1.714 + 1.715 +void 1.716 +FMRadioService::NotifyFMRadioEvent(FMRadioEventType aType) 1.717 +{ 1.718 + mObserverList.Broadcast(aType); 1.719 +} 1.720 + 1.721 +void 1.722 +FMRadioService::Notify(const FMRadioOperationInformation& aInfo) 1.723 +{ 1.724 + switch (aInfo.operation()) { 1.725 + case FM_RADIO_OPERATION_ENABLE: 1.726 + MOZ_ASSERT(IsFMRadioOn()); 1.727 + MOZ_ASSERT(mState == Disabling || mState == Enabling); 1.728 + 1.729 + // If we're disabling, disable the radio right now. 1.730 + if (mState == Disabling) { 1.731 + DoDisable(); 1.732 + return; 1.733 + } 1.734 + 1.735 + // Fire success callback on the enable request. 1.736 + TransitionState(SuccessResponse(), Enabled); 1.737 + 1.738 + // To make sure the FM app will get the right frequency after the FM 1.739 + // radio is enabled, we have to set the frequency first. 1.740 + SetFMRadioFrequency(mPendingFrequencyInKHz); 1.741 + 1.742 + // Bug 949855: enable audio after the FM radio HW is enabled, to make sure 1.743 + // 'hw.fm.isAnalog' could be detected as |true| during first time launch. 1.744 + // This case is for audio output on analog path, i.e. 'ro.moz.fm.noAnalog' 1.745 + // is not |true|. 1.746 + EnableAudio(true); 1.747 + 1.748 + // Update the current frequency without sending the`FrequencyChanged` 1.749 + // event, to make sure the FM app will get the right frequency when the 1.750 + // `EnabledChange` event is sent. 1.751 + mPendingFrequencyInKHz = GetFMRadioFrequency(); 1.752 + UpdatePowerState(); 1.753 + 1.754 + // The frequency was changed from '0' to some meaningful number, so we 1.755 + // should send the `FrequencyChanged` event manually. 1.756 + NotifyFMRadioEvent(FrequencyChanged); 1.757 + break; 1.758 + case FM_RADIO_OPERATION_DISABLE: 1.759 + MOZ_ASSERT(mState == Disabling); 1.760 + 1.761 + TransitionState(SuccessResponse(), Disabled); 1.762 + UpdatePowerState(); 1.763 + break; 1.764 + case FM_RADIO_OPERATION_SEEK: 1.765 + 1.766 + // Seek action might be cancelled by SetFrequency(), we need to check if 1.767 + // the current state is Seeking. 1.768 + if (mState == Seeking) { 1.769 + TransitionState(SuccessResponse(), Enabled); 1.770 + } 1.771 + 1.772 + UpdateFrequency(); 1.773 + break; 1.774 + case FM_RADIO_OPERATION_TUNE: 1.775 + UpdateFrequency(); 1.776 + break; 1.777 + default: 1.778 + MOZ_CRASH(); 1.779 + } 1.780 +} 1.781 + 1.782 +void 1.783 +FMRadioService::UpdatePowerState() 1.784 +{ 1.785 + bool enabled = IsFMRadioOn(); 1.786 + if (enabled != mEnabled) { 1.787 + mEnabled = enabled; 1.788 + NotifyFMRadioEvent(EnabledChanged); 1.789 + } 1.790 +} 1.791 + 1.792 +void 1.793 +FMRadioService::UpdateFrequency() 1.794 +{ 1.795 + int32_t frequency = GetFMRadioFrequency(); 1.796 + if (mPendingFrequencyInKHz != frequency) { 1.797 + mPendingFrequencyInKHz = frequency; 1.798 + NotifyFMRadioEvent(FrequencyChanged); 1.799 + } 1.800 +} 1.801 + 1.802 +// static 1.803 +FMRadioService* 1.804 +FMRadioService::Singleton() 1.805 +{ 1.806 + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); 1.807 + MOZ_ASSERT(NS_IsMainThread()); 1.808 + 1.809 + if (!sFMRadioService) { 1.810 + sFMRadioService = new FMRadioService(); 1.811 + } 1.812 + 1.813 + return sFMRadioService; 1.814 +} 1.815 + 1.816 +NS_IMPL_ISUPPORTS(FMRadioService, nsIObserver) 1.817 + 1.818 +END_FMRADIO_NAMESPACE 1.819 +