Bug 854753 - [B2G][Audio] Implement SpeakerManager API. r=amarchesini, sr=jonas
authorRandy Lin <rlin@mozilla.com>
Mon, 25 Nov 2013 12:50:03 +1300
changeset 158028 1f5ec27b648d2a556ad639799a219068c866b40a
parent 158027 49f62fb6007b8be244c1cfe7157e1c1e5274de9a
child 158029 3d937509b0fe6900e718d13354b1af167dac30b6
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewersamarchesini, jonas
bugs854753
milestone28.0a1
Bug 854753 - [B2G][Audio] Implement SpeakerManager API. r=amarchesini, sr=jonas
CLOBBER
content/base/src/nsGkAtomList.h
content/events/public/nsEventNameList.h
dom/apps/src/PermissionsTable.jsm
dom/audiochannel/AudioChannelService.cpp
dom/audiochannel/AudioChannelService.h
dom/audiochannel/AudioChannelServiceChild.cpp
dom/bindings/Bindings.conf
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PContent.ipdl
dom/moz.build
dom/speakermanager/SpeakerManager.cpp
dom/speakermanager/SpeakerManager.h
dom/speakermanager/SpeakerManagerService.cpp
dom/speakermanager/SpeakerManagerService.h
dom/speakermanager/SpeakerManagerServiceChild.cpp
dom/speakermanager/SpeakerManagerServiceChild.h
dom/speakermanager/moz.build
dom/speakermanager/tests/mochitest.ini
dom/speakermanager/tests/test_speakermanager.html
dom/tests/mochitest/general/test_interfaces.html
dom/webidl/MozSpeakerManager.webidl
dom/webidl/moz.build
layout/build/Makefile.in
layout/build/nsLayoutStatics.cpp
widget/BasicEvents.h
--- a/CLOBBER
+++ b/CLOBBER
@@ -13,9 +13,9 @@
 #          |               |
 #          O <-- Clobber   O  <-- Clobber
 #
 # Note: The description below will be part of the error message shown to users.
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
-Bug 921918 - need clobber for Windows.
+Bug 854753 - need clobber for Windows for new webidl changes.
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -707,16 +707,17 @@ GK_ATOM(ondragover, "ondragover")
 GK_ATOM(ondragstart, "ondragstart")
 GK_ATOM(ondrop, "ondrop")
 GK_ATOM(onenabled, "onenabled")
 GK_ATOM(onemergencycbmodechange, "onemergencycbmodechange")
 GK_ATOM(onerror, "onerror")
 GK_ATOM(onfailed, "onfailed")
 GK_ATOM(onfocus, "onfocus")
 GK_ATOM(onfrequencychange, "onfrequencychange")
+GK_ATOM(onspeakerforcedchange, "onspeakerforcedchange")
 GK_ATOM(onget, "onget")
 GK_ATOM(ongroupchange, "ongroupchange")
 GK_ATOM(onhashchange, "onhashchange")
 GK_ATOM(onheadphoneschange, "onheadphoneschange")
 GK_ATOM(onheld, "onheld")
 GK_ATOM(onhfpstatuschanged, "onhfpstatuschanged")
 GK_ATOM(onholding, "onholding")
 GK_ATOM(oniccchange, "oniccchange")
--- a/content/events/public/nsEventNameList.h
+++ b/content/events/public/nsEventNameList.h
@@ -649,16 +649,21 @@ NON_IDL_EVENT(stop,
               EventNameType_None,
               NS_EVENT)
 
 NON_IDL_EVENT(warning,
               NS_MEDIARECORDER_WARNING,
               EventNameType_None,
               NS_EVENT)
 
+NON_IDL_EVENT(speakerforcedchange,
+              NS_SPEAKERMANAGER_SPEAKERFORCEDCHANGE,
+              EventNameType_None,
+              NS_EVENT)
+
 // Events that only have on* attributes on XUL elements
 NON_IDL_EVENT(text,
               NS_TEXT_TEXT,
               EventNameType_XUL,
               NS_EVENT)
 NON_IDL_EVENT(compositionstart,
               NS_COMPOSITION_START,
               EventNameType_XUL,
--- a/dom/apps/src/PermissionsTable.jsm
+++ b/dom/apps/src/PermissionsTable.jsm
@@ -308,16 +308,21 @@ this.PermissionsTable =  { geolocation: 
                              certified: ALLOW_ACTION,
                              access: ["read", "write"]
                            },
                            "nfc-manager": {
                              app: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
+                           "speaker-control": {
+                             app: DENY_ACTION,
+                             privileged: ALLOW_ACTION,
+                             certified: ALLOW_ACTION
+                           },
                          };
 
 /**
  * Append access modes to the permission name as suffixes.
  *   e.g. permission name 'contacts' with ['read', 'write'] =
  *   ['contacts-read', contacts-write']
  * @param string aPermName
  * @param array aAccess
--- a/dom/audiochannel/AudioChannelService.cpp
+++ b/dom/audiochannel/AudioChannelService.cpp
@@ -20,16 +20,17 @@
 #include "nsHashPropertyBag.h"
 #include "nsComponentManagerUtils.h"
 #include "nsServiceManagerUtils.h"
 
 #ifdef MOZ_WIDGET_GONK
 #include "nsJSUtils.h"
 #include "nsCxPusher.h"
 #include "nsIAudioManager.h"
+#include "SpeakerManagerService.h"
 #define NS_AUDIOMANAGER_CONTRACTID "@mozilla.org/telephony/audiomanager;1"
 #endif
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::hal;
 
 StaticRefPtr<AudioChannelService> gAudioChannelService;
@@ -165,16 +166,22 @@ AudioChannelService::UnregisterAudioChan
 
   nsAutoPtr<AudioChannelAgentData> data;
   mAgents.RemoveAndForget(aAgent, data);
 
   if (data) {
     UnregisterType(data->mType, data->mElementHidden,
                    CONTENT_PROCESS_ID_MAIN, data->mWithVideo);
   }
+#ifdef MOZ_WIDGET_GONK
+  bool active = AnyAudioChannelIsActive();
+  for (uint32_t i = 0; i < mSpeakerManager.Length(); i++) {
+    mSpeakerManager[i]->SetAudioChannelActive(active);
+  }
+#endif
 }
 
 void
 AudioChannelService::UnregisterType(AudioChannelType aType,
                                     bool aElementHidden,
                                     uint64_t aChildID,
                                     bool aWithVideo)
 {
@@ -555,16 +562,29 @@ NS_IMETHODIMP
 AudioChannelService::Notify(nsITimer* aTimer)
 {
   UnregisterTypeInternal(AUDIO_CHANNEL_TELEPHONY, mTimerElementHidden, mTimerChildID, false);
   mDeferTelChannelTimer = nullptr;
   return NS_OK;
 }
 
 bool
+AudioChannelService::AnyAudioChannelIsActive()
+{
+  for (int i = AUDIO_CHANNEL_INT_LAST - 1;
+       i >= AUDIO_CHANNEL_INT_NORMAL; --i) {
+    if (!mChannelCounters[i].IsEmpty()) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+bool
 AudioChannelService::ChannelsActiveWithHigherPriorityThan(
   AudioChannelInternalType aType)
 {
   for (int i = AUDIO_CHANNEL_INT_LAST - 1;
        i != AUDIO_CHANNEL_INT_CONTENT_HIDDEN; --i) {
     if (i == aType) {
       return false;
     }
--- a/dom/audiochannel/AudioChannelService.h
+++ b/dom/audiochannel/AudioChannelService.h
@@ -13,17 +13,19 @@
 #include "nsITimer.h"
 
 #include "AudioChannelCommon.h"
 #include "AudioChannelAgent.h"
 #include "nsClassHashtable.h"
 
 namespace mozilla {
 namespace dom {
-
+#ifdef MOZ_WIDGET_GONK
+class SpeakerManagerService;
+#endif
 class AudioChannelService
 : public nsIObserver
 , public nsITimerCallback
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
   NS_DECL_NSITIMERCALLBACK
@@ -76,16 +78,31 @@ public:
 
   /***
    * AudioChannelManager calls this function to notify the default channel used
    * to adjust volume when there is no any active channel.
    */
   virtual void SetDefaultVolumeControlChannel(AudioChannelType aType,
                                               bool aHidden);
 
+  bool AnyAudioChannelIsActive();
+
+#ifdef MOZ_WIDGET_GONK
+  void RegisterSpeakerManager(SpeakerManagerService* aSpeakerManager)
+  {
+    if (!mSpeakerManager.Contains(aSpeakerManager)) {
+      mSpeakerManager.AppendElement(aSpeakerManager);
+    }
+  }
+
+  void UnregisterSpeakerManager(SpeakerManagerService* aSpeakerManager)
+  {
+    mSpeakerManager.RemoveElement(aSpeakerManager);
+  }
+#endif
 protected:
   void Notify();
 
   /**
    * Send the audio-channel-changed notification for the given process ID if
    * needed.
    */
   void SendAudioChannelChangedNotification(uint64_t aChildID);
@@ -158,17 +175,19 @@ protected:
     const bool mWithVideo;
   };
 
   static PLDHashOperator
   NotifyEnumerator(AudioChannelAgent* aAgent,
                    AudioChannelAgentData* aData, void *aUnused);
 
   nsClassHashtable< nsPtrHashKey<AudioChannelAgent>, AudioChannelAgentData > mAgents;
-
+#ifdef MOZ_WIDGET_GONK
+  nsTArray<SpeakerManagerService*>  mSpeakerManager;
+#endif
   nsTArray<uint64_t> mChannelCounters[AUDIO_CHANNEL_INT_LAST];
 
   AudioChannelType mCurrentHigherChannel;
   AudioChannelType mCurrentVisibleHigherChannel;
 
   nsTArray<uint64_t> mWithVideoChildIDs;
 
   // mPlayableHiddenContentChildID stores the ChildID of the process which can
--- a/dom/audiochannel/AudioChannelServiceChild.cpp
+++ b/dom/audiochannel/AudioChannelServiceChild.cpp
@@ -12,16 +12,20 @@
 #include "mozilla/StaticPtr.h"
 #include "mozilla/unused.h"
 #include "mozilla/Util.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/ContentParent.h"
 #include "nsIObserverService.h"
 #include "nsThreadUtils.h"
 
+#ifdef MOZ_WIDGET_GONK
+#include "SpeakerManagerService.h"
+#endif
+
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::hal;
 
 StaticRefPtr<AudioChannelServiceChild> gAudioChannelServiceChild;
 
 // static
 AudioChannelService*
@@ -115,16 +119,22 @@ AudioChannelServiceChild::UnregisterAudi
 
   ContentChild::GetSingleton()->SendAudioChannelUnregisterType(
       data.mType, data.mElementHidden, data.mWithVideo);
 
   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   if (obs) {
     obs->NotifyObservers(nullptr, "audio-channel-agent-changed", nullptr);
   }
+#ifdef MOZ_WIDGET_GONK
+  bool active = AnyAudioChannelIsActive();
+  for (uint32_t i = 0; i < mSpeakerManager.Length(); i++) {
+    mSpeakerManager[i]->SetAudioChannelActive(active);
+  }
+#endif
 }
 
 void
 AudioChannelServiceChild::SetDefaultVolumeControlChannel(
   AudioChannelType aType, bool aHidden)
 {
   ContentChild *cc = ContentChild::GetSingleton();
   if (cc) {
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -797,16 +797,21 @@ DOMInterfaces = {
     'nativeType': 'mozilla::dom::network::MobileConnectionArray',
     'resultNotAddRefed': [ 'item' ]
 },
 
 'MozNamedAttrMap': {
     'nativeType': 'nsDOMAttributeMap',
 },
 
+'MozSpeakerManager': {
+    'nativeType': 'mozilla::dom::SpeakerManager',
+    'headerFile': 'SpeakerManager.h'
+},
+
 'MozPowerManager': {
     'nativeType': 'mozilla::dom::PowerManager',
 },
 
 'MozTimeManager': {
     'nativeType': 'mozilla::dom::time::TimeManager',
 },
 
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -76,16 +76,17 @@
 
 #if defined(MOZ_WIDGET_ANDROID)
 #include "APKOpen.h"
 #endif
 
 #if defined(MOZ_WIDGET_GONK)
 #include "nsVolume.h"
 #include "nsVolumeService.h"
+#include "SpeakerManagerService.h"
 #endif
 
 #ifdef XP_WIN
 #include <process.h>
 #define getpid _getpid
 #endif
 
 #ifdef ACCESSIBILITY
@@ -581,16 +582,30 @@ ContentChild::RecvSetProcessPrivileges(c
   // time if/when possible. SetCurrentProcessPrivileges should probably be
   // moved as well. Right now this is set ONLY if we receive the
   // RecvSetProcessPrivileges message. See bug 880808.
   SetCurrentProcessSandbox();
 #endif
   return true;
 }
 
+bool
+ContentChild::RecvSpeakerManagerNotify()
+{
+#ifdef MOZ_WIDGET_GONK
+  nsRefPtr<SpeakerManagerService> service =
+    SpeakerManagerService::GetSpeakerManagerService();
+  if (service) {
+    service->Notify();
+  }
+  return true;
+#endif
+  return false;
+}
+
 static CancelableTask* sFirstIdleTask;
 
 static void FirstIdle(void)
 {
     MOZ_ASSERT(sFirstIdleTask);
     sFirstIdleTask = nullptr;
     ContentChild::GetSingleton()->SendFirstIdle();
 }
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -173,16 +173,18 @@ public:
                                     const InfallibleTArray<OverrideMapping>& overrides,
                                     const nsCString& locale);
 
     virtual mozilla::jsipc::PJavaScriptChild* AllocPJavaScriptChild();
     virtual bool DeallocPJavaScriptChild(mozilla::jsipc::PJavaScriptChild*);
 
     virtual bool RecvSetOffline(const bool& offline);
 
+    virtual bool RecvSpeakerManagerNotify();
+
     virtual bool RecvNotifyVisited(const URIParams& aURI);
     // auto remove when alertfinished is received.
     nsresult AddRemoteAlertObserver(const nsString& aData, nsIObserver* aObserver);
 
     virtual bool RecvPreferenceUpdate(const PrefSetting& aPref);
 
     virtual bool RecvNotifyAlertsObserver(const nsCString& aType, const nsString& aData);
 
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -108,16 +108,17 @@
 
 #ifdef MOZ_WIDGET_ANDROID
 # include "AndroidBridge.h"
 #endif
 
 #ifdef MOZ_WIDGET_GONK
 #include "nsIVolume.h"
 #include "nsIVolumeService.h"
+#include "SpeakerManagerService.h"
 using namespace mozilla::system;
 #endif
 
 #ifdef MOZ_B2G_BT
 #include "BluetoothParent.h"
 #include "BluetoothService.h"
 #endif
 
@@ -2474,16 +2475,45 @@ ContentParent::RecvPSpeechSynthesisConst
 #ifdef MOZ_WEBSPEECH
     return true;
 #else
     return false;
 #endif
 }
 
 bool
+ContentParent::RecvSpeakerManagerGetSpeakerStatus(bool* aValue)
+{
+#ifdef MOZ_WIDGET_GONK
+  *aValue = false;
+  nsRefPtr<SpeakerManagerService> service =
+    SpeakerManagerService::GetSpeakerManagerService();
+  if (service) {
+    *aValue = service->GetSpeakerStatus();
+  }
+  return true;
+#endif
+  return false;
+}
+
+bool
+ContentParent::RecvSpeakerManagerForceSpeaker(const bool& aEnable)
+{
+#ifdef MOZ_WIDGET_GONK
+  nsRefPtr<SpeakerManagerService> service =
+    SpeakerManagerService::GetSpeakerManagerService();
+  if (service) {
+    service->ForceSpeaker(aEnable, mChildID);
+  }
+  return true;
+#endif
+  return false;
+}
+
+bool
 ContentParent::RecvStartVisitedQuery(const URIParams& aURI)
 {
     nsCOMPtr<nsIURI> newURI = DeserializeURI(aURI);
     if (!newURI) {
         return false;
     }
     nsCOMPtr<IHistory> history = services::GetHistoryService();
     if (history) {
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -469,16 +469,20 @@ private:
 
     virtual bool RecvAudioChannelChangedNotification();
 
     virtual bool RecvAudioChannelChangeDefVolChannel(
       const AudioChannelType& aType, const bool& aHidden);
 
     virtual bool RecvBroadcastVolume(const nsString& aVolumeName);
 
+    virtual bool RecvSpeakerManagerGetSpeakerStatus(bool* aValue);
+
+    virtual bool RecvSpeakerManagerForceSpeaker(const bool& aEnable);
+
     virtual bool RecvSystemMessageHandled() MOZ_OVERRIDE;
 
     virtual bool RecvNuwaReady() MOZ_OVERRIDE;
 
     virtual bool RecvAddNewProcess(const uint32_t& aPid,
                                    const InfallibleTArray<ProtocolFdMapping>& aFds) MOZ_OVERRIDE;
 
     virtual bool RecvCreateFakeVolume(const nsString& fsName, const nsString& mountPoint) MOZ_OVERRIDE;
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -233,26 +233,27 @@ child:
 
     PMemoryReportRequest(uint32_t generation);
 
     /**
      * Notify the AudioChannelService in the child processes.
      */
     async AudioChannelNotify();
 
+    async SpeakerManagerNotify();
+
     /**
      * Do a memory info dump to a file in our temp directory.
      *
      * For documentation on the args, see
      * MemoryInfoDumper::dumpMemoryInfoToTempDir.
      */
     async DumpMemoryInfoToTempDir(nsString identifier,
                                   bool minimizeMemoryUsage,
                                   bool dumpChildProcesses);
-
     /**
      * Dump this process's GC and CC logs.
      *
      * For documentation on the args, see
      * MemoryInfoDumper::dumpGCAndCCLogsToFile.
      */
     async DumpGCAndCCLogsToFile(nsString identifier,
                                 bool dumpAllTraces,
@@ -466,15 +467,20 @@ parent:
 
     // called by the child (test code only) to propagate volume changes to the parent
     async CreateFakeVolume(nsString fsName, nsString mountPoint);
     async SetFakeVolumeState(nsString fsName, int32_t fsState);
 
     sync KeywordToURI(nsCString keyword)
         returns (OptionalInputStreamParams postData, OptionalURIParams uri);
 
+    sync SpeakerManagerForceSpeaker(bool aEnable);
+
+    sync SpeakerManagerGetSpeakerStatus()
+        returns (bool value);
+
 both:
      AsyncMessage(nsString aMessage, ClonedMessageData aData,
                   CpowEntry[] aCpows, Principal aPrincipal);
 };
 
 }
 }
--- a/dom/moz.build
+++ b/dom/moz.build
@@ -76,17 +76,20 @@ PARALLEL_DIRS += [
     'inputmethod',
     'webidl',
 ]
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     PARALLEL_DIRS += ['plugins/ipc/hangui']
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
-    PARALLEL_DIRS += ['wifi']
+    PARALLEL_DIRS += [
+        'speakermanager',
+        'wifi',
+    ]
 
 if CONFIG['MOZ_B2G_RIL']:
     PARALLEL_DIRS += [
         'icc',
         'cellbroadcast',
         'voicemail',
         'wappush',
     ]
new file mode 100644
--- /dev/null
+++ b/dom/speakermanager/SpeakerManager.cpp
@@ -0,0 +1,219 @@
+/* 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 "nsIDOMClassInfo.h"
+#include "nsIDOMEventListener.h"
+#include "SpeakerManagerService.h"
+#include "nsIPermissionManager.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIDocShell.h"
+#include "nsDOMEvent.h"
+#include "AudioChannelService.h"
+
+using namespace mozilla::dom;
+
+NS_IMPL_QUERY_INTERFACE_INHERITED1(SpeakerManager, nsDOMEventTargetHelper,
+                                   nsIDOMEventListener)
+NS_IMPL_ADDREF_INHERITED(SpeakerManager, nsDOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(SpeakerManager, nsDOMEventTargetHelper)
+
+SpeakerManager::SpeakerManager()
+  : mForcespeaker(false)
+  , mVisible(false)
+{
+  SetIsDOMBinding();
+  SpeakerManagerService *service =
+    SpeakerManagerService::GetSpeakerManagerService();
+  if (service) {
+    service->RegisterSpeakerManager(this);
+  }
+}
+
+SpeakerManager::~SpeakerManager()
+{
+  SpeakerManagerService *service = SpeakerManagerService::GetSpeakerManagerService();
+  if (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::GetSpeakerManagerService();
+  if (service) {
+    return service->GetSpeakerStatus();
+  }
+  return false;
+}
+
+bool
+SpeakerManager::Forcespeaker()
+{
+  return mForcespeaker;
+}
+
+void
+SpeakerManager::SetForcespeaker(bool aEnable)
+{
+  SpeakerManagerService *service = SpeakerManagerService::GetSpeakerManagerService();
+  if (service) {
+    service->ForceSpeaker(aEnable, mVisible);
+  }
+  mForcespeaker = aEnable;
+}
+
+void
+SpeakerManager::DispatchSimpleEvent(const nsAString& aStr)
+{
+  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
+  nsresult rv = CheckInnerWindowCorrectness();
+  if (NS_FAILED(rv)) {
+    return;
+  }
+
+  nsCOMPtr<nsIDOMEvent> event;
+  rv = NS_NewDOMEvent(getter_AddRefs(event), this, nullptr, nullptr);
+  if (NS_FAILED(rv)) {
+    NS_WARNING("Failed to create the error event!!!");
+    return;
+  }
+  rv = event->InitEvent(aStr, false, false);
+
+  if (NS_FAILED(rv)) {
+    NS_WARNING("Failed to init the error event!!!");
+    return;
+  }
+
+  event->SetTrusted(true);
+
+  rv = DispatchDOMEvent(nullptr, event, nullptr, nullptr);
+  if (NS_FAILED(rv)) {
+    NS_ERROR("Failed to dispatch the event!!!");
+    return;
+  }
+}
+
+void
+SpeakerManager::Init(nsPIDOMWindow* aWindow)
+{
+  BindToOwner(aWindow->IsOuterWindow() ?
+    aWindow->GetCurrentInnerWindow() : aWindow);
+
+  mVisible = !GetOwner()->IsBackground();
+  nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(GetOwner());
+  NS_ENSURE_TRUE_VOID(target);
+
+  target->AddSystemEventListener(NS_LITERAL_STRING("visibilitychange"),
+                                 this,
+                                 /* useCapture = */ true,
+                                 /* wantsUntrusted = */ false);
+}
+
+nsPIDOMWindow*
+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<nsPIDOMWindow> ownerWindow = do_QueryInterface(aGlobal.GetAsSupports());
+  if (!ownerWindow) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIPermissionManager> permMgr =
+    do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
+  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;
+  }
+
+  nsRefPtr<SpeakerManager> object = new SpeakerManager();
+  object->Init(ownerWindow);
+  return object.forget();
+}
+
+JSObject*
+SpeakerManager::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
+{
+  return MozSpeakerManagerBinding::Wrap(aCx, aScope, this);
+}
+
+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::GetSpeakerManagerService();
+  if (service && 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) {
+    AudioChannelService* audioChannelService =
+      AudioChannelService::GetAudioChannelService();
+    if (audioChannelService && !audioChannelService->AnyAudioChannelIsActive()) {
+      service->ForceSpeaker(false, mVisible);
+    }
+  }
+  return NS_OK;
+}
+
+void
+SpeakerManager::SetAudioChannelActive(bool isActive)
+{
+  if (!isActive && !mVisible) {
+    SpeakerManagerService *service =
+      SpeakerManagerService::GetSpeakerManagerService();
+    if (service) {
+      service->ForceSpeaker(false, mVisible);
+    }
+  }
+}
new file mode 100644
--- /dev/null
+++ b/dom/speakermanager/SpeakerManager.h
@@ -0,0 +1,65 @@
+/* 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 "nsDOMEventTargetHelper.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 MOZ_FINAL
+  : public nsDOMEventTargetHelper
+  , public nsIDOMEventListener
+{
+  friend class SpeakerManagerService;
+  friend class SpeakerManagerServiceChild;
+
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_NSIDOMEVENTLISTENER
+
+public:
+  void Init(nsPIDOMWindow* aWindow);
+
+  nsPIDOMWindow* GetParentObject() const;
+
+  virtual JSObject* WrapObject(JSContext* aCx,
+                               JS::Handle<JSObject*> aScope) MOZ_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
new file mode 100644
--- /dev/null
+++ b/dom/speakermanager/SpeakerManagerService.cpp
@@ -0,0 +1,199 @@
+/* -*- 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 "SpeakerManagerService.h"
+#include "SpeakerManagerServiceChild.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/unused.h"
+#include "mozilla/Util.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::GetSpeakerManagerService()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (XRE_GetProcessType() != GeckoProcessType_Default) {
+    return SpeakerManagerServiceChild::GetSpeakerManagerService();
+  }
+
+  // If we already exist, exit early
+  if (gSpeakerManagerService) {
+    return gSpeakerManagerService;
+  }
+
+  // Create new instance, register, return
+  nsRefPtr<SpeakerManagerService> service = new SpeakerManagerService();
+  NS_ENSURE_TRUE(service, nullptr);
+
+  gSpeakerManagerService = service;
+  return gSpeakerManagerService;
+}
+
+void
+SpeakerManagerService::Shutdown()
+{
+  if (XRE_GetProcessType() != GeckoProcessType_Default) {
+    return SpeakerManagerServiceChild::Shutdown();
+  }
+
+  if (gSpeakerManagerService) {
+    gSpeakerManagerService = nullptr;
+  }
+}
+
+NS_IMPL_ISUPPORTS1(SpeakerManagerService, nsIObserver)
+
+void
+SpeakerManagerService::ForceSpeaker(bool aEnable, uint64_t aChildId)
+{
+  TuruOnSpeaker(aEnable);
+  if (aEnable) {
+    mSpeakerStatusSet.Put(aChildId);
+  }
+  Notify();
+  return;
+}
+
+void
+SpeakerManagerService::ForceSpeaker(bool aEnable, bool aVisible)
+{
+  // b2g main process without oop
+  TuruOnSpeaker(aEnable && aVisible);
+  mVisible = aVisible;
+  mOrgSpeakerStatus = aEnable;
+  Notify();
+}
+
+void
+SpeakerManagerService::TuruOnSpeaker(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 PRUnichar* 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)) {
+          TuruOnSpeaker(false);
+          mSpeakerStatusSet.Remove(childID);
+        }
+        if (mOrgSpeakerStatus) {
+          TuruOnSpeaker(!mOrgSpeakerStatus);
+          mOrgSpeakerStatus = false;
+        }
+    } else {
+      NS_WARNING("ipc:content-shutdown message without childID property");
+    }
+  }
+  return NS_OK;
+}
+
+SpeakerManagerService::SpeakerManagerService()
+  : mOrgSpeakerStatus(false),
+    mVisible(false)
+{
+  MOZ_COUNT_CTOR(SpeakerManagerService);
+  if (XRE_GetProcessType() == GeckoProcessType_Default) {
+    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+    if (obs) {
+      obs->AddObserver(this, "ipc:content-shutdown", false);
+    }
+  }
+  AudioChannelService* audioChannelService =
+    AudioChannelService::GetAudioChannelService();
+  if (audioChannelService) {
+    audioChannelService->RegisterSpeakerManager(this);
+  }
+}
+
+SpeakerManagerService::~SpeakerManagerService()
+{
+  MOZ_COUNT_DTOR(SpeakerManagerService);
+  AudioChannelService* audioChannelService =
+    AudioChannelService::GetAudioChannelService();
+  if (audioChannelService)
+    audioChannelService->UnregisterSpeakerManager(this);
+}
new file mode 100644
--- /dev/null
+++ b/dom/speakermanager/SpeakerManagerService.h
@@ -0,0 +1,72 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_SpeakerManagerService_h__
+#define mozilla_dom_SpeakerManagerService_h__
+
+#include "nsAutoPtr.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
+
+  static SpeakerManagerService* GetSpeakerManagerService();
+  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);
+  }
+  /**
+   * Shutdown the singleton.
+   */
+  static void Shutdown();
+
+protected:
+  SpeakerManagerService();
+
+  virtual ~SpeakerManagerService();
+  // Notify to UA if device speaker status changed
+  virtual void Notify();
+
+  void TuruOnSpeaker(bool aEnable);
+
+  nsTArray<nsRefPtr<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
new file mode 100644
--- /dev/null
+++ b/dom/speakermanager/SpeakerManagerServiceChild.cpp
@@ -0,0 +1,113 @@
+/* -*- 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 "SpeakerManagerServiceChild.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/unused.h"
+#include "mozilla/Util.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::GetSpeakerManagerService()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // If we already exist, exit early
+  if (gSpeakerManagerServiceChild) {
+    return gSpeakerManagerServiceChild;
+  }
+
+  // Create new instance, register, return
+  nsRefPtr<SpeakerManagerServiceChild> service = new SpeakerManagerServiceChild();
+  NS_ENSURE_TRUE(service, nullptr);
+
+  gSpeakerManagerServiceChild = service;
+  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());
+  AudioChannelService* audioChannelService = AudioChannelService::GetAudioChannelService();
+  if (audioChannelService) {
+    audioChannelService->RegisterSpeakerManager(this);
+  }
+  MOZ_COUNT_CTOR(SpeakerManagerServiceChild);
+}
+
+SpeakerManagerServiceChild::~SpeakerManagerServiceChild()
+{
+  AudioChannelService* audioChannelService = AudioChannelService::GetAudioChannelService();
+  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"));
+  }
+}
new file mode 100644
--- /dev/null
+++ b/dom/speakermanager/SpeakerManagerServiceChild.h
@@ -0,0 +1,37 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_SpeakerManagerServicechild_h__
+#define mozilla_dom_SpeakerManagerServicechild_h__
+
+#include "nsAutoPtr.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:
+  static SpeakerManagerService* GetSpeakerManagerService();
+  static void Shutdown();
+  virtual void ForceSpeaker(bool aEnable, bool aVisible) MOZ_OVERRIDE;
+  virtual bool GetSpeakerStatus() MOZ_OVERRIDE;
+  virtual void SetAudioChannelActive(bool aIsActive) MOZ_OVERRIDE;
+  virtual void Notify() MOZ_OVERRIDE;
+protected:
+  SpeakerManagerServiceChild();
+  virtual ~SpeakerManagerServiceChild();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
+
new file mode 100644
--- /dev/null
+++ b/dom/speakermanager/moz.build
@@ -0,0 +1,27 @@
+# -*- Mode: python; c-basic-offset: 4; 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']
+
+XPIDL_MODULE = 'dom_speakermanager'
+
+EXPORTS += [
+    'SpeakerManager.h',
+    'SpeakerManagerService.h',
+    'SpeakerManagerServiceChild.h',
+]
+
+SOURCES += [
+    'SpeakerManager.cpp',
+    'SpeakerManagerService.cpp',
+    'SpeakerManagerServiceChild.cpp',
+]
+
+FAIL_ON_WARNINGS = True
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'gklayout'
new file mode 100644
--- /dev/null
+++ b/dom/speakermanager/tests/mochitest.ini
@@ -0,0 +1,3 @@
+[DEFAULT]
+
+[test_speakermanager.html]
new file mode 100644
--- /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, "mozAlarms on Firefox OS only.");
+      SimpleTest.finish();
+    }
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  SpecialPowers.pushPermissions(
+    [{ "type": "speaker-control", "allow": 1, "context": document }],
+    startTests);
+
+  </script>
+</pre>
+</body>
+</html>
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -369,16 +369,17 @@ var interfaceNamesInGlobalScope =
     {name: "mozRTCIceCandidate", pref: "media.peerconnection.enabled"},
     {name: "mozRTCPeerConnection", pref: "media.peerconnection.enabled"},
     {name: "mozRTCSessionDescription", pref: "media.peerconnection.enabled"},
     "MozSettingsEvent",
     "MozSmsEvent",
     "MozSmsFilter",
     "MozSmsMessage",
     "MozSmsSegmentInfo",
+    {name: "MozSpeakerManager", b2g: true},
     {name: "MozStkCommandEvent", b2g: true, pref: "dom.icc.enabled"},
     {name: "MozTimeManager", b2g: true},
     {name: "MozVoicemail", b2g: true, pref: "dom.voicemail.enabled"},
     {name: "MozVoicemailEvent", b2g: true, pref: "dom.voicemail.enabled"},
     "MozWakeLock",
     {name: "MozWifiConnectionInfoEvent", b2g: true},
     {name: "MozWifiStatusChangeEvent", b2g: true},
     "MutationEvent",
new file mode 100644
--- /dev/null
+++ b/dom/webidl/MozSpeakerManager.webidl
@@ -0,0 +1,18 @@
+/* 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/.
+ */
+
+/*
+ * Allow application can control acoustic sound output through speaker.
+ * Reference https://wiki.mozilla.org/WebAPI/SpeakerManager
+ */
+[Constructor()]
+interface MozSpeakerManager : EventTarget {
+  /* query the speaker status */
+  readonly attribute boolean speakerforced;
+  /* force device device's acoustic sound output through speaker */
+  attribute boolean forcespeaker;
+  /* this event will be fired when device's speaker forced status change */
+  attribute EventHandler onspeakerforcedchange;
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -527,16 +527,17 @@ if CONFIG['MOZ_NFC']:
          'MozNdefRecord.webidl',
          'MozNfc.webidl',
          'MozNFCPeer.webidl',
          'MozNFCTag.webidl',
     ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
     WEBIDL_FILES += [
+        'MozSpeakerManager.webidl',
         'MozWifiConnectionInfoEvent.webidl',
         'MozWifiStatusChangeEvent.webidl',
     ]
 
 if CONFIG['MOZ_WEBSPEECH']:
     WEBIDL_FILES += [
         'SpeechRecognitionError.webidl',
         'SpeechRecognitionEvent.webidl',
--- a/layout/build/Makefile.in
+++ b/layout/build/Makefile.in
@@ -73,16 +73,17 @@ LOCAL_INCLUDES	+= -I$(srcdir)/../base \
 		   -I$(topsrcdir)/dom/src/json \
 		   -I$(topsrcdir)/dom/src/jsurl \
 		   -I$(topsrcdir)/dom/src/storage \
 		   -I$(topsrcdir)/dom/src/offline \
 		   -I$(topsrcdir)/dom/src/geolocation \
 		   -I$(topsrcdir)/dom/audiochannel \
 		   -I$(topsrcdir)/dom/telephony \
 		   -I$(topsrcdir)/dom/media \
+		   -I$(topsrcdir)/dom/speakermanager \
 		   -I. \
 		   -I$(topsrcdir)/editor/libeditor/base \
 		   -I$(topsrcdir)/editor/libeditor/text \
 		   -I$(topsrcdir)/editor/libeditor/html \
 		   -I$(topsrcdir)/editor/txmgr/src \
 		   -I$(topsrcdir)/editor/txtsvc/src \
 		   -I$(topsrcdir)/editor/composer/src \
 		   -I$(topsrcdir)/js/xpconnect/src \
@@ -95,16 +96,17 @@ LOCAL_INCLUDES	+= -I$(srcdir)/../base \
 		   $(NULL)
 
 ifdef MOZ_GSTREAMER
 LOCAL_INCLUDES	+= $(GSTREAMER_CFLAGS)
 endif
 
 ifeq (gonk,$(MOZ_WIDGET_TOOLKIT))
 LOCAL_INCLUDES	+= -I$(topsrcdir)/dom/system/gonk
+LOCAL_INCLUDES	+= -I$(topsrcdir)/dom/speakermanager
 endif #}
 
 ifdef MOZ_B2G_FM #{
 LOCAL_INCLUDES	+= -I$(topsrcdir)/dom/fmradio
 endif #}
 
 ifdef MOZ_B2G_BT #{
 LOCAL_INCLUDES	+= -I$(topsrcdir)/dom/bluetooth
--- a/layout/build/nsLayoutStatics.cpp
+++ b/layout/build/nsLayoutStatics.cpp
@@ -95,16 +95,17 @@
 #endif
 
 #include "AudioStream.h"
 #include "Latency.h"
 #include "WebAudioUtils.h"
 
 #ifdef MOZ_WIDGET_GONK
 #include "nsVolumeService.h"
+#include "SpeakerManagerService.h"
 using namespace mozilla::system;
 #endif
 
 #include "nsError.h"
 
 #include "nsJSEnvironment.h"
 #include "nsContentSink.h"
 #include "nsFrameMessageManager.h"
@@ -367,16 +368,17 @@ nsLayoutStatics::Shutdown()
   WebAudioUtils::Shutdown();
 
 #ifdef MOZ_WMF
   WMFDecoder::UnloadDLLs();
 #endif
 
 #ifdef MOZ_WIDGET_GONK
   nsVolumeService::Shutdown();
+  SpeakerManagerService::Shutdown();
 #endif
 
 #ifdef MOZ_WEBSPEECH
   nsSynthVoiceRegistry::Shutdown();
 #endif
 
   nsCORSListenerProxy::Shutdown();
 
--- a/widget/BasicEvents.h
+++ b/widget/BasicEvents.h
@@ -455,16 +455,20 @@ enum nsEventStructType
 #define NS_NETWORK_DOWNLOAD_EVENT    (NS_NETWORK_EVENT_START + 2)
 
 // MediaRecorder events.
 #define NS_MEDIARECORDER_EVENT_START 5700
 #define NS_MEDIARECORDER_DATAAVAILABLE  (NS_MEDIARECORDER_EVENT_START + 1)
 #define NS_MEDIARECORDER_WARNING        (NS_MEDIARECORDER_EVENT_START + 2)
 #define NS_MEDIARECORDER_STOP           (NS_MEDIARECORDER_EVENT_START + 3)
 
+// SpeakerManager events
+#define NS_SPEAKERMANAGER_EVENT_START 5800
+#define NS_SPEAKERMANAGER_SPEAKERFORCEDCHANGE (NS_SPEAKERMANAGER_EVENT_START + 1)
+
 #ifdef MOZ_GAMEPAD
 // Gamepad input events
 #define NS_GAMEPAD_START         6000
 #define NS_GAMEPAD_BUTTONDOWN    (NS_GAMEPAD_START)
 #define NS_GAMEPAD_BUTTONUP      (NS_GAMEPAD_START+1)
 #define NS_GAMEPAD_AXISMOVE      (NS_GAMEPAD_START+2)
 #define NS_GAMEPAD_CONNECTED     (NS_GAMEPAD_START+3)
 #define NS_GAMEPAD_DISCONNECTED  (NS_GAMEPAD_START+4)