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 173214 1f5ec27b648d2a556ad639799a219068c866b40a
parent 173213 49f62fb6007b8be244c1cfe7157e1c1e5274de9a
child 173215 3d937509b0fe6900e718d13354b1af167dac30b6
push id445
push userffxbld
push dateMon, 10 Mar 2014 22:05:19 +0000
treeherdermozilla-release@dc38b741b04e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersamarchesini, jonas
bugs854753
milestone28.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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)