summaryrefslogtreecommitdiffstats
path: root/dom/speakermanager
diff options
context:
space:
mode:
Diffstat (limited to 'dom/speakermanager')
-rw-r--r--dom/speakermanager/SpeakerManager.cpp217
-rw-r--r--dom/speakermanager/SpeakerManager.h66
-rw-r--r--dom/speakermanager/SpeakerManagerService.cpp224
-rw-r--r--dom/speakermanager/SpeakerManagerService.h79
-rw-r--r--dom/speakermanager/SpeakerManagerServiceChild.cpp121
-rw-r--r--dom/speakermanager/SpeakerManagerServiceChild.h44
-rw-r--r--dom/speakermanager/moz.build23
-rw-r--r--dom/speakermanager/tests/mochitest.ini3
-rw-r--r--dom/speakermanager/tests/test_speakermanager.html53
9 files changed, 830 insertions, 0 deletions
diff --git a/dom/speakermanager/SpeakerManager.cpp b/dom/speakermanager/SpeakerManager.cpp
new file mode 100644
index 000000000..2b30fa72c
--- /dev/null
+++ b/dom/speakermanager/SpeakerManager.cpp
@@ -0,0 +1,217 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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 "SpeakerManager.h"
+
+#include "mozilla/Services.h"
+
+#include "mozilla/dom/Event.h"
+
+#include "AudioChannelService.h"
+#include "nsIDocShell.h"
+#include "nsIDOMClassInfo.h"
+#include "nsIDOMEventListener.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIPermissionManager.h"
+#include "SpeakerManagerService.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_QUERY_INTERFACE_INHERITED(SpeakerManager, DOMEventTargetHelper,
+ nsIDOMEventListener)
+NS_IMPL_ADDREF_INHERITED(SpeakerManager, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(SpeakerManager, DOMEventTargetHelper)
+
+SpeakerManager::SpeakerManager()
+ : mForcespeaker(false)
+ , mVisible(false)
+{
+ SpeakerManagerService *service =
+ SpeakerManagerService::GetOrCreateSpeakerManagerService();
+ MOZ_ASSERT(service);
+ service->RegisterSpeakerManager(this);
+}
+
+SpeakerManager::~SpeakerManager()
+{
+ SpeakerManagerService *service = SpeakerManagerService::GetOrCreateSpeakerManagerService();
+ MOZ_ASSERT(service);
+
+ service->UnRegisterSpeakerManager(this);
+ nsCOMPtr<EventTarget> target = do_QueryInterface(GetOwner());
+ NS_ENSURE_TRUE_VOID(target);
+
+ target->RemoveSystemEventListener(NS_LITERAL_STRING("visibilitychange"),
+ this,
+ /* useCapture = */ true);
+}
+
+bool
+SpeakerManager::Speakerforced()
+{
+ // If a background app calls forcespeaker=true that doesn't change anything.
+ // 'speakerforced' remains false everywhere.
+ if (mForcespeaker && !mVisible) {
+ return false;
+ }
+ SpeakerManagerService *service = SpeakerManagerService::GetOrCreateSpeakerManagerService();
+ MOZ_ASSERT(service);
+ return service->GetSpeakerStatus();
+
+}
+
+bool
+SpeakerManager::Forcespeaker()
+{
+ return mForcespeaker;
+}
+
+void
+SpeakerManager::SetForcespeaker(bool aEnable)
+{
+ SpeakerManagerService *service = SpeakerManagerService::GetOrCreateSpeakerManagerService();
+ MOZ_ASSERT(service);
+
+ service->ForceSpeaker(aEnable, mVisible);
+ mForcespeaker = aEnable;
+}
+
+void
+SpeakerManager::DispatchSimpleEvent(const nsAString& aStr)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
+ nsresult rv = CheckInnerWindowCorrectness();
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
+ event->InitEvent(aStr, false, false);
+ event->SetTrusted(true);
+
+ rv = DispatchDOMEvent(nullptr, event, nullptr, nullptr);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("Failed to dispatch the event!!!");
+ return;
+ }
+}
+
+void
+SpeakerManager::Init(nsPIDOMWindowInner* aWindow)
+{
+ BindToOwner(aWindow);
+
+ nsCOMPtr<nsIDocShell> docshell = GetOwner()->GetDocShell();
+ NS_ENSURE_TRUE_VOID(docshell);
+ docshell->GetIsActive(&mVisible);
+
+ nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(GetOwner());
+ NS_ENSURE_TRUE_VOID(target);
+
+ target->AddSystemEventListener(NS_LITERAL_STRING("visibilitychange"),
+ this,
+ /* useCapture = */ true,
+ /* wantsUntrusted = */ false);
+}
+
+nsPIDOMWindowInner*
+SpeakerManager::GetParentObject() const
+{
+ return GetOwner();
+}
+
+/* static */ already_AddRefed<SpeakerManager>
+SpeakerManager::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv)
+{
+ nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aGlobal.GetAsSupports());
+ if (!sgo) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> ownerWindow = do_QueryInterface(aGlobal.GetAsSupports());
+ if (!ownerWindow) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIPermissionManager> permMgr = services::GetPermissionManager();
+ NS_ENSURE_TRUE(permMgr, nullptr);
+
+ uint32_t permission = nsIPermissionManager::DENY_ACTION;
+ nsresult rv =
+ permMgr->TestPermissionFromWindow(ownerWindow, "speaker-control",
+ &permission);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ if (permission != nsIPermissionManager::ALLOW_ACTION) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ RefPtr<SpeakerManager> object = new SpeakerManager();
+ object->Init(ownerWindow);
+ return object.forget();
+}
+
+JSObject*
+SpeakerManager::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return MozSpeakerManagerBinding::Wrap(aCx, this, aGivenProto);
+}
+
+NS_IMETHODIMP
+SpeakerManager::HandleEvent(nsIDOMEvent* aEvent)
+{
+ nsAutoString type;
+ aEvent->GetType(type);
+
+ if (!type.EqualsLiteral("visibilitychange")) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIDocShell> docshell = do_GetInterface(GetOwner());
+ NS_ENSURE_TRUE(docshell, NS_ERROR_FAILURE);
+ docshell->GetIsActive(&mVisible);
+
+ // If an app that has called forcespeaker=true is switched
+ // from the background to the foreground 'speakerforced'
+ // switches to true in all apps. I.e. the app doesn't have to
+ // call forcespeaker=true again when it comes into foreground.
+ SpeakerManagerService *service =
+ SpeakerManagerService::GetOrCreateSpeakerManagerService();
+ MOZ_ASSERT(service);
+
+ if (mVisible && mForcespeaker) {
+ service->ForceSpeaker(mForcespeaker, mVisible);
+ }
+ // If an application that has called forcespeaker=true, but no audio is
+ // currently playing in the app itself, if application switch to
+ // the background, we switch 'speakerforced' to false.
+ if (!mVisible && mForcespeaker) {
+ RefPtr<AudioChannelService> audioChannelService =
+ AudioChannelService::GetOrCreate();
+ if (audioChannelService && !audioChannelService->AnyAudioChannelIsActive()) {
+ service->ForceSpeaker(false, mVisible);
+ }
+ }
+ return NS_OK;
+}
+
+void
+SpeakerManager::SetAudioChannelActive(bool isActive)
+{
+ if (mForcespeaker) {
+ SpeakerManagerService *service =
+ SpeakerManagerService::GetOrCreateSpeakerManagerService();
+ MOZ_ASSERT(service);
+ service->ForceSpeaker(isActive, mVisible);
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/speakermanager/SpeakerManager.h b/dom/speakermanager/SpeakerManager.h
new file mode 100644
index 000000000..bf084dfd9
--- /dev/null
+++ b/dom/speakermanager/SpeakerManager.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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/. */
+
+#ifndef mozilla_dom_SpeakerManager_h
+#define mozilla_dom_SpeakerManager_h
+
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/MozSpeakerManagerBinding.h"
+
+namespace mozilla {
+namespace dom {
+/* This class is used for UA to control devices's speaker status.
+ * After UA set the speaker status, the UA should handle the
+ * forcespeakerchange event and change the speaker status in UI.
+ * The device's speaker status would set back to normal when UA close the application.
+ */
+class SpeakerManager final
+ : public DOMEventTargetHelper
+ , public nsIDOMEventListener
+{
+ friend class SpeakerManagerService;
+ friend class SpeakerManagerServiceChild;
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIDOMEVENTLISTENER
+
+public:
+ void Init(nsPIDOMWindowInner* aWindow);
+
+ nsPIDOMWindowInner* GetParentObject() const;
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+ /**
+ * WebIDL Interface
+ */
+ // Get this api's force speaker setting.
+ bool Forcespeaker();
+ // Force acoustic sound go through speaker. Don't force to speaker if application
+ // stay in the background and re-force when application
+ // go to foreground
+ void SetForcespeaker(bool aEnable);
+ // Get the device's speaker forced setting.
+ bool Speakerforced();
+
+ void SetAudioChannelActive(bool aIsActive);
+ IMPL_EVENT_HANDLER(speakerforcedchange)
+
+ static already_AddRefed<SpeakerManager>
+ Constructor(const GlobalObject& aGlobal, ErrorResult& aRv);
+
+protected:
+ SpeakerManager();
+ ~SpeakerManager();
+ void DispatchSimpleEvent(const nsAString& aStr);
+ // This api's force speaker setting
+ bool mForcespeaker;
+ bool mVisible;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_SpeakerManager_h
diff --git a/dom/speakermanager/SpeakerManagerService.cpp b/dom/speakermanager/SpeakerManagerService.cpp
new file mode 100644
index 000000000..a444f7163
--- /dev/null
+++ b/dom/speakermanager/SpeakerManagerService.cpp
@@ -0,0 +1,224 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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 "SpeakerManagerService.h"
+#include "SpeakerManagerServiceChild.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/ContentParent.h"
+#include "nsIPropertyBag2.h"
+#include "nsThreadUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "AudioChannelService.h"
+#include <cutils/properties.h>
+
+#define NS_AUDIOMANAGER_CONTRACTID "@mozilla.org/telephony/audiomanager;1"
+#include "nsIAudioManager.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+StaticRefPtr<SpeakerManagerService> gSpeakerManagerService;
+
+// static
+SpeakerManagerService*
+SpeakerManagerService::GetOrCreateSpeakerManagerService()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!XRE_IsParentProcess()) {
+ return SpeakerManagerServiceChild::GetOrCreateSpeakerManagerService();
+ }
+
+ // If we already exist, exit early
+ if (gSpeakerManagerService) {
+ return gSpeakerManagerService;
+ }
+
+ // Create new instance, register, return
+ RefPtr<SpeakerManagerService> service = new SpeakerManagerService();
+
+ gSpeakerManagerService = service;
+
+ return gSpeakerManagerService;
+}
+
+SpeakerManagerService*
+SpeakerManagerService::GetSpeakerManagerService()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!XRE_IsParentProcess()) {
+ return SpeakerManagerServiceChild::GetSpeakerManagerService();
+ }
+
+ return gSpeakerManagerService;
+}
+
+void
+SpeakerManagerService::Shutdown()
+{
+ if (!XRE_IsParentProcess()) {
+ return SpeakerManagerServiceChild::Shutdown();
+ }
+
+ if (gSpeakerManagerService) {
+ gSpeakerManagerService = nullptr;
+ }
+}
+
+NS_IMPL_ISUPPORTS(SpeakerManagerService, nsIObserver)
+
+void
+SpeakerManagerService::ForceSpeaker(bool aEnable, uint64_t aChildId)
+{
+ TurnOnSpeaker(aEnable);
+ if (aEnable) {
+ mSpeakerStatusSet.Put(aChildId);
+ }
+ Notify();
+ return;
+}
+
+void
+SpeakerManagerService::ForceSpeaker(bool aEnable, bool aVisible)
+{
+ // b2g main process without oop
+ TurnOnSpeaker(aEnable && aVisible);
+ mVisible = aVisible;
+ mOrgSpeakerStatus = aEnable;
+ Notify();
+}
+
+void
+SpeakerManagerService::TurnOnSpeaker(bool aOn)
+{
+ nsCOMPtr<nsIAudioManager> audioManager = do_GetService(NS_AUDIOMANAGER_CONTRACTID);
+ NS_ENSURE_TRUE_VOID(audioManager);
+ int32_t phoneState;
+ audioManager->GetPhoneState(&phoneState);
+ int32_t forceuse = (phoneState == nsIAudioManager::PHONE_STATE_IN_CALL ||
+ phoneState == nsIAudioManager::PHONE_STATE_IN_COMMUNICATION)
+ ? nsIAudioManager::USE_COMMUNICATION : nsIAudioManager::USE_MEDIA;
+ if (aOn) {
+ audioManager->SetForceForUse(forceuse, nsIAudioManager::FORCE_SPEAKER);
+ } else {
+ audioManager->SetForceForUse(forceuse, nsIAudioManager::FORCE_NONE);
+ }
+}
+
+bool
+SpeakerManagerService::GetSpeakerStatus()
+{
+ char propQemu[PROPERTY_VALUE_MAX];
+ property_get("ro.kernel.qemu", propQemu, "");
+ if (!strncmp(propQemu, "1", 1)) {
+ return mOrgSpeakerStatus;
+ }
+ nsCOMPtr<nsIAudioManager> audioManager = do_GetService(NS_AUDIOMANAGER_CONTRACTID);
+ NS_ENSURE_TRUE(audioManager, false);
+ int32_t usage;
+ audioManager->GetForceForUse(nsIAudioManager::USE_MEDIA, &usage);
+ return usage == nsIAudioManager::FORCE_SPEAKER;
+}
+
+void
+SpeakerManagerService::Notify()
+{
+ // Parent Notify to all the child processes.
+ nsTArray<ContentParent*> children;
+ ContentParent::GetAll(children);
+ for (uint32_t i = 0; i < children.Length(); i++) {
+ Unused << children[i]->SendSpeakerManagerNotify();
+ }
+
+ for (uint32_t i = 0; i < mRegisteredSpeakerManagers.Length(); i++) {
+ mRegisteredSpeakerManagers[i]->
+ DispatchSimpleEvent(NS_LITERAL_STRING("speakerforcedchange"));
+ }
+}
+
+void
+SpeakerManagerService::SetAudioChannelActive(bool aIsActive)
+{
+ if (!aIsActive && !mVisible) {
+ ForceSpeaker(!mOrgSpeakerStatus, mVisible);
+ }
+}
+
+NS_IMETHODIMP
+SpeakerManagerService::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData)
+{
+ if (!strcmp(aTopic, "ipc:content-shutdown")) {
+ nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aSubject);
+ if (!props) {
+ NS_WARNING("ipc:content-shutdown message without property bag as subject");
+ return NS_OK;
+ }
+
+ uint64_t childID = 0;
+ nsresult rv = props->GetPropertyAsUint64(NS_LITERAL_STRING("childID"),
+ &childID);
+ if (NS_SUCCEEDED(rv)) {
+ // If the audio has paused by audiochannel,
+ // the enable flag should be false and don't need to handle.
+ if (mSpeakerStatusSet.Contains(childID)) {
+ TurnOnSpeaker(false);
+ mSpeakerStatusSet.Remove(childID);
+ }
+ if (mOrgSpeakerStatus) {
+ TurnOnSpeaker(!mOrgSpeakerStatus);
+ mOrgSpeakerStatus = false;
+ }
+ } else {
+ NS_WARNING("ipc:content-shutdown message without childID property");
+ }
+ } else if (!strcmp(aTopic, "xpcom-will-shutdown")) {
+ // Note that we need to do this before xpcom-shutdown, since the
+ // AudioChannelService cannot be used past that point.
+ RefPtr<AudioChannelService> audioChannelService =
+ AudioChannelService::GetOrCreate();
+ if (audioChannelService) {
+ audioChannelService->UnregisterSpeakerManager(this);
+ }
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, "ipc:content-shutdown");
+ obs->RemoveObserver(this, "xpcom-will-shutdown");
+ }
+
+ Shutdown();
+ }
+ return NS_OK;
+}
+
+SpeakerManagerService::SpeakerManagerService()
+ : mOrgSpeakerStatus(false),
+ mVisible(false)
+{
+ MOZ_COUNT_CTOR(SpeakerManagerService);
+ if (XRE_IsParentProcess()) {
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->AddObserver(this, "ipc:content-shutdown", false);
+ obs->AddObserver(this, "xpcom-will-shutdown", false);
+ }
+ }
+ RefPtr<AudioChannelService> audioChannelService =
+ AudioChannelService::GetOrCreate();
+ if (audioChannelService) {
+ audioChannelService->RegisterSpeakerManager(this);
+ }
+}
+
+SpeakerManagerService::~SpeakerManagerService()
+{
+ MOZ_COUNT_DTOR(SpeakerManagerService);
+}
diff --git a/dom/speakermanager/SpeakerManagerService.h b/dom/speakermanager/SpeakerManagerService.h
new file mode 100644
index 000000000..5f3506ae2
--- /dev/null
+++ b/dom/speakermanager/SpeakerManagerService.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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/. */
+
+#ifndef mozilla_dom_SpeakerManagerService_h__
+#define mozilla_dom_SpeakerManagerService_h__
+
+#include "nsIObserver.h"
+#include "nsTArray.h"
+#include "SpeakerManager.h"
+#include "nsIAudioManager.h"
+#include "nsCheapSets.h"
+#include "nsHashKeys.h"
+
+namespace mozilla {
+namespace dom {
+
+class SpeakerManagerService : public nsIObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ /*
+ * Return null or instance which has been created.
+ */
+ static SpeakerManagerService* GetSpeakerManagerService();
+ /*
+ * Return SpeakerManagerService instance.
+ * If SpeakerManagerService is not exist, create and return new one.
+ */
+ static SpeakerManagerService* GetOrCreateSpeakerManagerService();
+ virtual void ForceSpeaker(bool aEnable, bool aVisible);
+ virtual bool GetSpeakerStatus();
+ virtual void SetAudioChannelActive(bool aIsActive);
+ // Called by child
+ void ForceSpeaker(bool enable, uint64_t aChildid);
+ // Register the SpeakerManager to service for notify the speakerforcedchange event
+ void RegisterSpeakerManager(SpeakerManager* aSpeakerManager)
+ {
+ mRegisteredSpeakerManagers.AppendElement(aSpeakerManager);
+ }
+ void UnRegisterSpeakerManager(SpeakerManager* aSpeakerManager)
+ {
+ mRegisteredSpeakerManagers.RemoveElement(aSpeakerManager);
+ }
+
+protected:
+ SpeakerManagerService();
+
+ virtual ~SpeakerManagerService();
+ // Notify to UA if device speaker status changed
+ virtual void Notify();
+
+ void TurnOnSpeaker(bool aEnable);
+
+ /**
+ * Shutdown the singleton.
+ */
+ static void Shutdown();
+
+ nsTArray<RefPtr<SpeakerManager> > mRegisteredSpeakerManagers;
+ // Set for remember all the child speaker status
+ nsCheapSet<nsUint64HashKey> mSpeakerStatusSet;
+ // The Speaker status assign by UA
+ bool mOrgSpeakerStatus;
+
+ bool mVisible;
+ // This is needed for IPC communication between
+ // SpeakerManagerServiceChild and this class.
+ friend class ContentParent;
+ friend class ContentChild;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/speakermanager/SpeakerManagerServiceChild.cpp b/dom/speakermanager/SpeakerManagerServiceChild.cpp
new file mode 100644
index 000000000..6c1e30b78
--- /dev/null
+++ b/dom/speakermanager/SpeakerManagerServiceChild.cpp
@@ -0,0 +1,121 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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 "SpeakerManagerServiceChild.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "nsIObserverService.h"
+#include "nsThreadUtils.h"
+#include "AudioChannelService.h"
+#include <cutils/properties.h>
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+StaticRefPtr<SpeakerManagerServiceChild> gSpeakerManagerServiceChild;
+
+// static
+SpeakerManagerService*
+SpeakerManagerServiceChild::GetOrCreateSpeakerManagerService()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // If we already exist, exit early
+ if (gSpeakerManagerServiceChild) {
+ return gSpeakerManagerServiceChild;
+ }
+
+ // Create new instance, register, return
+ RefPtr<SpeakerManagerServiceChild> service = new SpeakerManagerServiceChild();
+
+ gSpeakerManagerServiceChild = service;
+
+ return gSpeakerManagerServiceChild;
+}
+
+// static
+SpeakerManagerService*
+SpeakerManagerServiceChild::GetSpeakerManagerService()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return gSpeakerManagerServiceChild;
+}
+
+void
+SpeakerManagerServiceChild::ForceSpeaker(bool aEnable, bool aVisible)
+{
+ mVisible = aVisible;
+ mOrgSpeakerStatus = aEnable;
+ ContentChild *cc = ContentChild::GetSingleton();
+ if (cc) {
+ cc->SendSpeakerManagerForceSpeaker(aEnable && aVisible);
+ }
+}
+
+bool
+SpeakerManagerServiceChild::GetSpeakerStatus()
+{
+ ContentChild *cc = ContentChild::GetSingleton();
+ bool status = false;
+ if (cc) {
+ cc->SendSpeakerManagerGetSpeakerStatus(&status);
+ }
+ char propQemu[PROPERTY_VALUE_MAX];
+ property_get("ro.kernel.qemu", propQemu, "");
+ if (!strncmp(propQemu, "1", 1)) {
+ return mOrgSpeakerStatus;
+ }
+ return status;
+}
+
+void
+SpeakerManagerServiceChild::Shutdown()
+{
+ if (gSpeakerManagerServiceChild) {
+ gSpeakerManagerServiceChild = nullptr;
+ }
+}
+
+void
+SpeakerManagerServiceChild::SetAudioChannelActive(bool aIsActive)
+{
+ // Content process and switch to background with no audio and speaker forced.
+ // Then disable speaker
+ for (uint32_t i = 0; i < mRegisteredSpeakerManagers.Length(); i++) {
+ mRegisteredSpeakerManagers[i]->SetAudioChannelActive(aIsActive);
+ }
+}
+
+SpeakerManagerServiceChild::SpeakerManagerServiceChild()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ RefPtr<AudioChannelService> audioChannelService = AudioChannelService::GetOrCreate();
+ if (audioChannelService) {
+ audioChannelService->RegisterSpeakerManager(this);
+ }
+ MOZ_COUNT_CTOR(SpeakerManagerServiceChild);
+}
+
+SpeakerManagerServiceChild::~SpeakerManagerServiceChild()
+{
+ RefPtr<AudioChannelService> audioChannelService = AudioChannelService::GetOrCreate();
+ if (audioChannelService) {
+ audioChannelService->UnregisterSpeakerManager(this);
+ }
+ MOZ_COUNT_DTOR(SpeakerManagerServiceChild);
+}
+
+void
+SpeakerManagerServiceChild::Notify()
+{
+ for (uint32_t i = 0; i < mRegisteredSpeakerManagers.Length(); i++) {
+ mRegisteredSpeakerManagers[i]->DispatchSimpleEvent(NS_LITERAL_STRING("speakerforcedchange"));
+ }
+}
diff --git a/dom/speakermanager/SpeakerManagerServiceChild.h b/dom/speakermanager/SpeakerManagerServiceChild.h
new file mode 100644
index 000000000..5eb1c6afa
--- /dev/null
+++ b/dom/speakermanager/SpeakerManagerServiceChild.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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/. */
+
+#ifndef mozilla_dom_SpeakerManagerServicechild_h__
+#define mozilla_dom_SpeakerManagerServicechild_h__
+
+#include "nsISupports.h"
+#include "SpeakerManagerService.h"
+
+namespace mozilla {
+namespace dom {
+/* This class is used to do the IPC to enable/disable speaker status
+ Also handle the application speaker competition problem
+*/
+class SpeakerManagerServiceChild : public SpeakerManagerService
+{
+public:
+ /*
+ * Return null or instance which has been created.
+ */
+ static SpeakerManagerService* GetSpeakerManagerService();
+ /*
+ * Return SpeakerManagerServiceChild instance.
+ * If SpeakerManagerServiceChild is not exist, create and return new one.
+ */
+ static SpeakerManagerService* GetOrCreateSpeakerManagerService();
+ static void Shutdown();
+ virtual void ForceSpeaker(bool aEnable, bool aVisible) override;
+ virtual bool GetSpeakerStatus() override;
+ virtual void SetAudioChannelActive(bool aIsActive) override;
+ virtual void Notify() override;
+protected:
+ SpeakerManagerServiceChild();
+ virtual ~SpeakerManagerServiceChild();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
+
diff --git a/dom/speakermanager/moz.build b/dom/speakermanager/moz.build
new file mode 100644
index 000000000..22ed849fb
--- /dev/null
+++ b/dom/speakermanager/moz.build
@@ -0,0 +1,23 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
+
+EXPORTS += [
+ 'SpeakerManager.h',
+ 'SpeakerManagerService.h',
+ 'SpeakerManagerServiceChild.h',
+]
+
+UNIFIED_SOURCES += [
+ 'SpeakerManager.cpp',
+ 'SpeakerManagerService.cpp',
+ 'SpeakerManagerServiceChild.cpp',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
diff --git a/dom/speakermanager/tests/mochitest.ini b/dom/speakermanager/tests/mochitest.ini
new file mode 100644
index 000000000..bb272adc0
--- /dev/null
+++ b/dom/speakermanager/tests/mochitest.ini
@@ -0,0 +1,3 @@
+[DEFAULT]
+
+[test_speakermanager.html]
diff --git a/dom/speakermanager/tests/test_speakermanager.html b/dom/speakermanager/tests/test_speakermanager.html
new file mode 100644
index 000000000..509df5e84
--- /dev/null
+++ b/dom/speakermanager/tests/test_speakermanager.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test MozSpeakerManager API</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+ <script type="application/javascript">
+
+ "use strict";
+
+ function testObject() {
+ var mgr = new MozSpeakerManager();
+ var spkforced = false;
+ mgr.onspeakerforcedchange = function() {
+ if (spkforced) {
+ is(mgr.speakerforced, true, 'speaker should be true');
+ spkforced = false;
+ mgr.forcespeaker = false;
+ } else {
+ is(mgr.speakerforced, false, 'speaker should be false');
+ SimpleTest.finish();
+ }
+ }
+ spkforced = true;
+ mgr.forcespeaker = true;
+ }
+
+ function startTests() {
+ // Currently applicable only on FxOS
+ if (navigator.userAgent.indexOf("Mobile") != -1 &&
+ navigator.appVersion.indexOf("Android") == -1) {
+ testObject();
+ } else {
+ ok(true, "MozSpeakerManager on Firefox OS only.");
+ SimpleTest.finish();
+ }
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPermissions(
+ [{ "type": "speaker-control", "allow": 1, "context": document }],
+ startTests);
+
+ </script>
+</pre>
+</body>
+</html>