/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:set ts=2 sw=2 sts=2 et cindent: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/dom/FMRadio.h" #include "nsContentUtils.h" #include "mozilla/Hal.h" #include "mozilla/HalTypes.h" #include "mozilla/Preferences.h" #include "mozilla/dom/FMRadioBinding.h" #include "mozilla/dom/ContentChild.h" #include "mozilla/dom/PFMRadioChild.h" #include "mozilla/dom/FMRadioService.h" #include "DOMRequest.h" #include "nsDOMClassInfo.h" #include "nsIDocShell.h" #include "nsIInterfaceRequestorUtils.h" #include "nsIAudioManager.h" #undef LOG #define LOG(args...) FM_LOG("FMRadio", args) // The pref indicates if the device has an internal antenna. // If the pref is true, the antanna will be always available. #define DOM_FM_ANTENNA_INTERNAL_PREF "dom.fmradio.antenna.internal" using namespace mozilla::hal; using mozilla::Preferences; BEGIN_FMRADIO_NAMESPACE class FMRadioRequest MOZ_FINAL : public FMRadioReplyRunnable , public DOMRequest { public: NS_DECL_ISUPPORTS_INHERITED FMRadioRequest(nsPIDOMWindow* aWindow, FMRadio* aFMRadio) : DOMRequest(aWindow) , mType(FMRadioRequestArgs::T__None) { // |FMRadio| inherits from |nsIDOMEventTarget| and |nsISupportsWeakReference| // which both inherits from nsISupports, so |nsISupports| is an ambiguous // base of |FMRadio|, we have to cast |aFMRadio| to one of the base classes. mFMRadio = do_GetWeakReference(static_cast(aFMRadio)); } FMRadioRequest(nsPIDOMWindow* aWindow, FMRadio* aFMRadio, FMRadioRequestArgs::Type aType) : DOMRequest(aWindow) { MOZ_ASSERT(aType >= FMRadioRequestArgs::T__None && aType <= FMRadioRequestArgs::T__Last, "Wrong FMRadioRequestArgs in FMRadioRequest"); mFMRadio = do_GetWeakReference(static_cast(aFMRadio)); mType = aType; } ~FMRadioRequest() { } NS_IMETHODIMP Run() { MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); nsCOMPtr target = do_QueryReferent(mFMRadio); if (!target) { return NS_OK; } FMRadio* fmRadio = static_cast( static_cast(target)); if (fmRadio->mIsShutdown) { return NS_OK; } switch (mResponseType.type()) { case FMRadioResponseType::TErrorResponse: FireError(mResponseType.get_ErrorResponse().error()); break; case FMRadioResponseType::TSuccessResponse: if (mType == FMRadioRequestArgs::TEnableRequestArgs) { fmRadio->EnableAudioChannelAgent(); } FireSuccess(JS::UndefinedHandleValue); break; default: MOZ_CRASH(); } return NS_OK; } private: FMRadioRequestArgs::Type mType; nsWeakPtr mFMRadio; }; NS_IMPL_ISUPPORTS_INHERITED0(FMRadioRequest, DOMRequest) FMRadio::FMRadio() : mHeadphoneState(SWITCH_STATE_OFF) , mAudioChannelAgentEnabled(false) , mHasInternalAntenna(false) , mIsShutdown(false) { LOG("FMRadio is initialized."); SetIsDOMBinding(); } FMRadio::~FMRadio() { } void FMRadio::Init(nsPIDOMWindow *aWindow) { BindToOwner(aWindow); IFMRadioService::Singleton()->AddObserver(this); mHasInternalAntenna = Preferences::GetBool(DOM_FM_ANTENNA_INTERNAL_PREF, /* default = */ false); if (mHasInternalAntenna) { LOG("We have an internal antenna."); } else { mHeadphoneState = GetCurrentSwitchState(SWITCH_HEADPHONES); RegisterSwitchObserver(SWITCH_HEADPHONES, this); } nsCOMPtr target = do_QueryInterface(GetOwner()); NS_ENSURE_TRUE_VOID(target); target->AddSystemEventListener(NS_LITERAL_STRING("visibilitychange"), this, /* useCapture = */ true, /* wantsUntrusted = */ false); // All of the codes below are for AudioChannel. We can directly return here // if preferences doesn't enable AudioChannelService. NS_ENSURE_TRUE_VOID(Preferences::GetBool("media.useAudioChannelService")); nsCOMPtr audioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1"); NS_ENSURE_TRUE_VOID(audioChannelAgent); audioChannelAgent->InitWithWeakCallback( GetOwner(), nsIAudioChannelAgent::AUDIO_AGENT_CHANNEL_CONTENT, this); nsCOMPtr docshell = do_GetInterface(GetOwner()); NS_ENSURE_TRUE_VOID(docshell); bool isActive = false; docshell->GetIsActive(&isActive); audioChannelAgent->SetVisibilityState(isActive); // Once all necessary resources are got successfully, we just enabled // mAudioChannelAgent. mAudioChannelAgent = audioChannelAgent; } void FMRadio::Shutdown() { IFMRadioService::Singleton()->RemoveObserver(this); if (!mHasInternalAntenna) { UnregisterSwitchObserver(SWITCH_HEADPHONES, this); } nsCOMPtr target = do_QueryInterface(GetOwner()); NS_ENSURE_TRUE_VOID(target); target->RemoveSystemEventListener(NS_LITERAL_STRING("visibilitychange"), this, /* useCapture = */ true); mIsShutdown = true; } JSObject* FMRadio::WrapObject(JSContext* aCx, JS::Handle aScope) { return FMRadioBinding::Wrap(aCx, aScope, this); } void FMRadio::Notify(const SwitchEvent& aEvent) { MOZ_ASSERT(!mHasInternalAntenna); if (mHeadphoneState != aEvent.status()) { mHeadphoneState = aEvent.status(); DispatchTrustedEvent(NS_LITERAL_STRING("antennaavailablechange")); } } void FMRadio::Notify(const FMRadioEventType& aType) { switch (aType) { case FrequencyChanged: DispatchTrustedEvent(NS_LITERAL_STRING("frequencychange")); break; case EnabledChanged: if (Enabled()) { DispatchTrustedEvent(NS_LITERAL_STRING("enabled")); } else { if (mAudioChannelAgentEnabled) { mAudioChannelAgent->StopPlaying(); mAudioChannelAgentEnabled = false; } DispatchTrustedEvent(NS_LITERAL_STRING("disabled")); } break; default: MOZ_CRASH(); } } /* static */ bool FMRadio::Enabled() { return IFMRadioService::Singleton()->IsEnabled(); } bool FMRadio::AntennaAvailable() const { return mHasInternalAntenna ? true : (mHeadphoneState != SWITCH_STATE_OFF) && (mHeadphoneState != SWITCH_STATE_UNKNOWN); } Nullable FMRadio::GetFrequency() const { return Enabled() ? Nullable(IFMRadioService::Singleton()->GetFrequency()) : Nullable(); } double FMRadio::FrequencyUpperBound() const { return IFMRadioService::Singleton()->GetFrequencyUpperBound(); } double FMRadio::FrequencyLowerBound() const { return IFMRadioService::Singleton()->GetFrequencyLowerBound(); } double FMRadio::ChannelWidth() const { return IFMRadioService::Singleton()->GetChannelWidth(); } already_AddRefed FMRadio::Enable(double aFrequency) { nsCOMPtr win = GetOwner(); if (!win) { return nullptr; } nsRefPtr r = new FMRadioRequest(win, this, FMRadioRequestArgs::TEnableRequestArgs); IFMRadioService::Singleton()->Enable(aFrequency, r); return r.forget(); } already_AddRefed FMRadio::Disable() { nsCOMPtr win = GetOwner(); if (!win) { return nullptr; } nsRefPtr r = new FMRadioRequest(win, this); IFMRadioService::Singleton()->Disable(r); return r.forget(); } already_AddRefed FMRadio::SetFrequency(double aFrequency) { nsCOMPtr win = GetOwner(); if (!win) { return nullptr; } nsRefPtr r = new FMRadioRequest(win, this); IFMRadioService::Singleton()->SetFrequency(aFrequency, r); return r.forget(); } already_AddRefed FMRadio::SeekUp() { nsCOMPtr win = GetOwner(); if (!win) { return nullptr; } nsRefPtr r = new FMRadioRequest(win, this); IFMRadioService::Singleton()->Seek(FM_RADIO_SEEK_DIRECTION_UP, r); return r.forget(); } already_AddRefed FMRadio::SeekDown() { nsCOMPtr win = GetOwner(); if (!win) { return nullptr; } nsRefPtr r = new FMRadioRequest(win, this); IFMRadioService::Singleton()->Seek(FM_RADIO_SEEK_DIRECTION_DOWN, r); return r.forget(); } already_AddRefed FMRadio::CancelSeek() { nsCOMPtr win = GetOwner(); if (!win) { return nullptr; } nsRefPtr r = new FMRadioRequest(win, this); IFMRadioService::Singleton()->CancelSeek(r); return r.forget(); } NS_IMETHODIMP FMRadio::HandleEvent(nsIDOMEvent* aEvent) { nsAutoString type; aEvent->GetType(type); if (!type.EqualsLiteral("visibilitychange")) { return NS_ERROR_FAILURE; } nsCOMPtr docshell = do_GetInterface(GetOwner()); NS_ENSURE_TRUE(docshell, NS_ERROR_FAILURE); bool isActive = false; docshell->GetIsActive(&isActive); mAudioChannelAgent->SetVisibilityState(isActive); return NS_OK; } void FMRadio::EnableAudioChannelAgent() { NS_ENSURE_TRUE_VOID(mAudioChannelAgent); int32_t playingState = 0; mAudioChannelAgent->StartPlaying(&playingState); SetCanPlay(playingState == AudioChannelState::AUDIO_CHANNEL_STATE_NORMAL); mAudioChannelAgentEnabled = true; } NS_IMETHODIMP FMRadio::CanPlayChanged(int32_t aCanPlay) { SetCanPlay(aCanPlay == AudioChannelState::AUDIO_CHANNEL_STATE_NORMAL); return NS_OK; } void FMRadio::SetCanPlay(bool aCanPlay) { IFMRadioService::Singleton()->EnableAudio(aCanPlay); } NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(FMRadio) NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgentCallback) NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper) NS_IMPL_ADDREF_INHERITED(FMRadio, nsDOMEventTargetHelper) NS_IMPL_RELEASE_INHERITED(FMRadio, nsDOMEventTargetHelper) END_FMRADIO_NAMESPACE