Bug 855655 - [AudioChannelManager] Add New Attribute for Setting Default Control Volume Channel. r=baku
authorMarco Chen <mchen@mozilla.com>
Thu, 12 Sep 2013 20:26:03 +0800
changeset 159811 4cfaef826ee6ac3dff53d2429c68ace4129657f7
parent 159810 58cbc13f9f2171927a0605387e96cfb256d8db55
child 159812 8ff506c57197f5dfbf87d937f77730936822d2d6
push id2961
push userlsblakk@mozilla.com
push dateMon, 28 Oct 2013 21:59:28 +0000
treeherdermozilla-beta@73ef4f13486f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs855655
milestone26.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 855655 - [AudioChannelManager] Add New Attribute for Setting Default Control Volume Channel. r=baku
b2g/chrome/content/shell.js
dom/audiochannel/AudioChannelCommon.h
dom/audiochannel/AudioChannelService.cpp
dom/audiochannel/AudioChannelService.h
dom/audiochannel/AudioChannelServiceChild.cpp
dom/audiochannel/AudioChannelServiceChild.h
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PContent.ipdl
dom/ipc/TabMessageUtils.h
dom/system/gonk/AudioChannelManager.cpp
dom/system/gonk/AudioChannelManager.h
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -1184,16 +1184,25 @@ window.addEventListener('ContentStart', 
   Services.obs.addObserver(function(aSubject, aTopic, aData) {
     shell.sendChromeEvent({
       type: 'audio-channel-changed',
       channel: aData
     });
 }, "audio-channel-changed", false);
 })();
 
+(function defaultVolumeChannelChangedTracker() {
+  Services.obs.addObserver(function(aSubject, aTopic, aData) {
+    shell.sendChromeEvent({
+      type: 'default-volume-channel-changed',
+      channel: aData
+    });
+}, "default-volume-channel-changed", false);
+})();
+
 (function visibleAudioChannelChangedTracker() {
   Services.obs.addObserver(function(aSubject, aTopic, aData) {
     shell.sendChromeEvent({
       type: 'visible-audio-channel-changed',
       channel: aData
     });
     shell.visibleNormalAudioActive = (aData == 'normal');
 }, "visible-audio-channel-changed", false);
--- a/dom/audiochannel/AudioChannelCommon.h
+++ b/dom/audiochannel/AudioChannelCommon.h
@@ -8,16 +8,17 @@
 #define mozilla_dom_audiochannelcommon_h__
 
 namespace mozilla {
 namespace dom {
 
 // The audio channel. Read the nsIHTMLMediaElement.idl for a description
 // about this attribute.
 enum AudioChannelType {
+  AUDIO_CHANNEL_DEFAULT = -1,
   AUDIO_CHANNEL_NORMAL = 0,
   AUDIO_CHANNEL_CONTENT,
   AUDIO_CHANNEL_NOTIFICATION,
   AUDIO_CHANNEL_ALARM,
   AUDIO_CHANNEL_TELEPHONY,
   AUDIO_CHANNEL_RINGER,
   AUDIO_CHANNEL_PUBLICNOTIFICATION,
   AUDIO_CHANNEL_LAST
--- a/dom/audiochannel/AudioChannelService.cpp
+++ b/dom/audiochannel/AudioChannelService.cpp
@@ -68,16 +68,17 @@ AudioChannelService::Shutdown()
 }
 
 NS_IMPL_ISUPPORTS2(AudioChannelService, nsIObserver, nsITimerCallback)
 
 AudioChannelService::AudioChannelService()
 : mCurrentHigherChannel(AUDIO_CHANNEL_LAST)
 , mCurrentVisibleHigherChannel(AUDIO_CHANNEL_LAST)
 , mActiveContentChildIDsFrozen(false)
+, mDefChannelChildID(CONTENT_PROCESS_ID_UNKNOWN)
 {
   if (XRE_GetProcessType() == GeckoProcessType_Default) {
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     if (obs) {
       obs->AddObserver(this, "ipc:content-shutdown", false);
 #ifdef MOZ_WIDGET_GONK
       // To monitor the volume settings based on audio channel.
       obs->AddObserver(this, "mozsettings-changed", false);
@@ -89,16 +90,18 @@ AudioChannelService::AudioChannelService
 AudioChannelService::~AudioChannelService()
 {
 }
 
 void
 AudioChannelService::RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
                                                AudioChannelType aType)
 {
+  MOZ_ASSERT(aType != AUDIO_CHANNEL_DEFAULT);
+
   AudioChannelAgentData* data = new AudioChannelAgentData(aType,
                                                           true /* mElementHidden */,
                                                           true /* mMuted */);
   mAgents.Put(aAgent, data);
   RegisterType(aType, CONTENT_PROCESS_ID_MAIN);
 }
 
 void
@@ -298,16 +301,46 @@ bool
 AudioChannelService::ProcessContentOrNormalChannelIsActive(uint64_t aChildID)
 {
   return mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].Contains(aChildID) ||
          mChannelCounters[AUDIO_CHANNEL_INT_CONTENT_HIDDEN].Contains(aChildID) ||
          mChannelCounters[AUDIO_CHANNEL_INT_NORMAL].Contains(aChildID);
 }
 
 void
+AudioChannelService::SetDefaultVolumeControlChannel(AudioChannelType aType,
+                                                    bool aHidden)
+{
+  SetDefaultVolumeControlChannelInternal(aType, aHidden, CONTENT_PROCESS_ID_MAIN);
+}
+
+void
+AudioChannelService::SetDefaultVolumeControlChannelInternal(
+  AudioChannelType aType, bool aHidden, uint64_t aChildID)
+{
+  if (XRE_GetProcessType() != GeckoProcessType_Default) {
+    return;
+  }
+
+  // If this child is in the background and mDefChannelChildID is set to
+  // others then it means other child in the foreground already set it's
+  // own default channel already.
+  if (!aHidden && mDefChannelChildID != aChildID) {
+    return;
+  }
+
+  mDefChannelChildID = aChildID;
+  nsString channelName;
+  channelName.AssignASCII(ChannelName(aType));
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  obs->NotifyObservers(nullptr, "default-volume-channel-changed",
+                       channelName.get());
+}
+
+void
 AudioChannelService::SendAudioChannelChangedNotification(uint64_t aChildID)
 {
   if (XRE_GetProcessType() != GeckoProcessType_Default) {
     return;
   }
 
   nsRefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
   props->SetPropertyAsUint64(NS_LITERAL_STRING("childID"), aChildID);
@@ -514,16 +547,22 @@ AudioChannelService::Observe(nsISupports
         }
       }
 
       // We don't have to remove the agents from the mAgents hashtable because if
       // that table contains only agents running on the same process.
 
       SendAudioChannelChangedNotification(childID);
       Notify();
+
+      if (mDefChannelChildID == childID) {
+        SetDefaultVolumeControlChannelInternal(AUDIO_CHANNEL_DEFAULT,
+                                               false, childID);
+        mDefChannelChildID = CONTENT_PROCESS_ID_UNKNOWN;
+      }
     } else {
       NS_WARNING("ipc:content-shutdown message without childID property");
     }
   }
 #ifdef MOZ_WIDGET_GONK
   // To process the volume control on each audio channel according to
   // change of settings
   else if (!strcmp(aTopic, "mozsettings-changed")) {
--- a/dom/audiochannel/AudioChannelService.h
+++ b/dom/audiochannel/AudioChannelService.h
@@ -43,17 +43,17 @@ public:
   /**
    * Any audio channel agent that starts playing should register itself to
    * this service, sharing the AudioChannelType.
    */
   virtual void RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
                                          AudioChannelType aType);
 
   /**
-   * Any  audio channel agent that stops playing should unregister itself to
+   * Any audio channel agent that stops playing should unregister itself to
    * this service.
    */
   virtual void UnregisterAudioChannelAgent(AudioChannelAgent* aAgent);
 
   /**
    * Return true if this type should be muted.
    */
   virtual bool GetMuted(AudioChannelAgent* aAgent, bool aElementHidden);
@@ -65,16 +65,23 @@ public:
   virtual bool ContentOrNormalChannelIsActive();
 
   /**
    * Return true iff a normal or content channel is active for the given process
    * ID.
    */
   virtual bool ProcessContentOrNormalChannelIsActive(uint64_t aChildID);
 
+  /***
+   * 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);
+
 protected:
   void Notify();
 
   /**
    * Send the audio-channel-changed notification for the given process ID if
    * needed.
    */
   void SendAudioChannelChangedNotification(uint64_t aChildID);
@@ -88,16 +95,20 @@ protected:
 
   bool GetMutedInternal(AudioChannelType aType, uint64_t aChildID,
                         bool aElementHidden, bool aElementWasHidden);
 
   /* Update the internal type value following the visibility changes */
   void UpdateChannelType(AudioChannelType aType, uint64_t aChildID,
                          bool aElementHidden, bool aElementWasHidden);
 
+  /* Send the default-volume-channel-changed notification */
+  void SetDefaultVolumeControlChannelInternal(AudioChannelType aType,
+                                              bool aHidden, uint64_t aChildID);
+
   AudioChannelService();
   virtual ~AudioChannelService();
 
   enum AudioChannelInternalType {
     AUDIO_CHANNEL_INT_NORMAL = 0,
     AUDIO_CHANNEL_INT_NORMAL_HIDDEN,
     AUDIO_CHANNEL_INT_CONTENT,
     AUDIO_CHANNEL_INT_CONTENT_HIDDEN,
@@ -149,16 +160,18 @@ protected:
 
   nsTArray<uint64_t> mActiveContentChildIDs;
   bool mActiveContentChildIDsFrozen;
 
   nsCOMPtr<nsITimer> mDeferTelChannelTimer;
   bool mTimerElementHidden;
   uint64_t mTimerChildID;
 
+  uint64_t mDefChannelChildID;
+
   // This is needed for IPC comunication between
   // AudioChannelServiceChild and this class.
   friend class ContentParent;
   friend class ContentChild;
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/audiochannel/AudioChannelServiceChild.cpp
+++ b/dom/audiochannel/AudioChannelServiceChild.cpp
@@ -86,16 +86,18 @@ AudioChannelServiceChild::GetMuted(Audio
 
   return muted;
 }
 
 void
 AudioChannelServiceChild::RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
                                                     AudioChannelType aType)
 {
+  MOZ_ASSERT(aType != AUDIO_CHANNEL_DEFAULT);
+
   AudioChannelService::RegisterAudioChannelAgent(aAgent, aType);
 
   ContentChild *cc = ContentChild::GetSingleton();
   if (cc) {
     cc->SendAudioChannelRegisterType(aType);
   }
 
   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
@@ -123,8 +125,18 @@ AudioChannelServiceChild::UnregisterAudi
     cc->SendAudioChannelUnregisterType(data.mType, data.mElementHidden);
   }
 
   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   if (obs) {
     obs->NotifyObservers(nullptr, "audio-channel-agent-changed", nullptr);
   }
 }
+
+void
+AudioChannelServiceChild::SetDefaultVolumeControlChannel(
+  AudioChannelType aType, bool aHidden)
+{
+  ContentChild *cc = ContentChild::GetSingleton();
+  if (cc) {
+    cc->SendAudioChannelChangeDefVolChannel(aType, aHidden);
+  }
+}
--- a/dom/audiochannel/AudioChannelServiceChild.h
+++ b/dom/audiochannel/AudioChannelServiceChild.h
@@ -33,16 +33,18 @@ public:
                                          AudioChannelType aType);
   virtual void UnregisterAudioChannelAgent(AudioChannelAgent* aAgent);
 
   /**
    * Return true if this type + this mozHidden should be muted.
    */
   virtual bool GetMuted(AudioChannelAgent* aAgent, bool aMozHidden);
 
+  virtual void SetDefaultVolumeControlChannel(AudioChannelType aType, bool aHidden);
+
 protected:
   AudioChannelServiceChild();
   virtual ~AudioChannelServiceChild();
 };
 
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -1625,16 +1625,29 @@ ContentParent::RecvAudioChannelChangedNo
         AudioChannelService::GetAudioChannelService();
     if (service) {
        service->SendAudioChannelChangedNotification(ChildID());
     }
     return true;
 }
 
 bool
+ContentParent::RecvAudioChannelChangeDefVolChannel(
+  const AudioChannelType& aType, const bool& aHidden)
+{
+    nsRefPtr<AudioChannelService> service =
+        AudioChannelService::GetAudioChannelService();
+    if (service) {
+       service->SetDefaultVolumeControlChannelInternal(aType,
+                                                       aHidden, mChildID);
+    }
+    return true;
+}
+
+bool
 ContentParent::RecvBroadcastVolume(const nsString& aVolumeName)
 {
 #ifdef MOZ_WIDGET_GONK
     nsresult rv;
     nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID, &rv);
     if (vs) {
         vs->BroadcastVolume(aVolumeName);
     }
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -420,16 +420,19 @@ private:
                                           bool* aValue);
 
     virtual bool RecvAudioChannelRegisterType(const AudioChannelType& aType);
     virtual bool RecvAudioChannelUnregisterType(const AudioChannelType& aType,
                                                 const bool& aElementHidden);
 
     virtual bool RecvAudioChannelChangedNotification();
 
+    virtual bool RecvAudioChannelChangeDefVolChannel(
+      const AudioChannelType& aType, const bool& aHidden);
+
     virtual bool RecvBroadcastVolume(const nsString& aVolumeName);
 
     virtual bool RecvRecordingDeviceEvents(const nsString& aRecordingStatus);
 
     virtual bool RecvSystemMessageHandled() MOZ_OVERRIDE;
 
     virtual bool RecvCreateFakeVolume(const nsString& fsName, const nsString& mountPoint) MOZ_OVERRIDE;
 
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -439,16 +439,18 @@ parent:
                               bool aElementWasHidden)
         returns (bool value);
 
     sync AudioChannelRegisterType(AudioChannelType aType);
     sync AudioChannelUnregisterType(AudioChannelType aType,
                                     bool aElementHidden);
 
     async AudioChannelChangedNotification();
+    async AudioChannelChangeDefVolChannel(AudioChannelType aType,
+                                          bool aHidden);
 
     async FilePathUpdateNotify(nsString aType,
                                nsString aStorageName,
                                nsString aFilepath,
                                nsCString aReason);
     // get nsIVolumeService to broadcast volume information
     async BroadcastVolume(nsString volumeName);
 
--- a/dom/ipc/TabMessageUtils.h
+++ b/dom/ipc/TabMessageUtils.h
@@ -56,16 +56,16 @@ struct ParamTraits<mozilla::dom::RemoteD
   static void Log(const paramType& aParam, std::wstring* aLog)
   {
   }
 };
 
 template <>
 struct ParamTraits<mozilla::dom::AudioChannelType>
   : public EnumSerializer<mozilla::dom::AudioChannelType,
-                          mozilla::dom::AUDIO_CHANNEL_NORMAL,
+                          mozilla::dom::AUDIO_CHANNEL_DEFAULT,
                           mozilla::dom::AUDIO_CHANNEL_LAST>
 { };
 
 }
 
 
 #endif
--- a/dom/system/gonk/AudioChannelManager.cpp
+++ b/dom/system/gonk/AudioChannelManager.cpp
@@ -1,51 +1,173 @@
 /* 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 "nsIDOMClassInfo.h"
+#include "nsIDOMEventListener.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDocShell.h"
+#include "nsIPermissionManager.h"
+#include "nsIInterfaceRequestorUtils.h"
 #include "AudioChannelManager.h"
-#include "nsIDOMClassInfo.h"
 #include "mozilla/dom/AudioChannelManagerBinding.h"
 
 using namespace mozilla::hal;
 
 namespace mozilla {
 namespace dom {
 namespace system {
 
+NS_IMPL_QUERY_INTERFACE_INHERITED1(AudioChannelManager, nsDOMEventTargetHelper,
+                                   nsIDOMEventListener)
+NS_IMPL_ADDREF_INHERITED(AudioChannelManager, nsDOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(AudioChannelManager, nsDOMEventTargetHelper)
+
 AudioChannelManager::AudioChannelManager()
   : mState(SWITCH_STATE_UNKNOWN)
+  , mVolumeChannel(AUDIO_CHANNEL_DEFAULT)
 {
   RegisterSwitchObserver(SWITCH_HEADPHONES, this);
   mState = GetCurrentSwitchState(SWITCH_HEADPHONES);
   SetIsDOMBinding();
 }
 
 AudioChannelManager::~AudioChannelManager()
 {
   UnregisterSwitchObserver(SWITCH_HEADPHONES, this);
+
+  nsCOMPtr<EventTarget> target = do_QueryInterface(GetOwner());
+  NS_ENSURE_TRUE_VOID(target);
+
+  target->RemoveSystemEventListener(NS_LITERAL_STRING("visibilitychange"),
+                                    this,
+                                    /* useCapture = */ true);
 }
 
 void
 AudioChannelManager::Init(nsPIDOMWindow* aWindow)
 {
   BindToOwner(aWindow->IsOuterWindow() ?
-    aWindow->GetCurrentInnerWindow() : aWindow);
+              aWindow->GetCurrentInnerWindow() : aWindow);
+
+  nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(GetOwner());
+  NS_ENSURE_TRUE_VOID(target);
+
+  target->AddSystemEventListener(NS_LITERAL_STRING("visibilitychange"),
+                                 this,
+                                 /* useCapture = */ true,
+                                 /* wantsUntrusted = */ false);
 }
 
 JSObject*
 AudioChannelManager::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
 {
   return AudioChannelManagerBinding::Wrap(aCx, aScope, this);
 }
 
 void
 AudioChannelManager::Notify(const SwitchEvent& aEvent)
 {
   mState = aEvent.status();
 
   DispatchTrustedEvent(NS_LITERAL_STRING("headphoneschange"));
 }
 
+bool
+AudioChannelManager::SetVolumeControlChannel(const nsAString& aChannel)
+{
+  if (aChannel.EqualsASCII("publicnotification")) {
+    return false;
+  }
+
+  AudioChannelType oldVolumeChannel = mVolumeChannel;
+  // Only normal channel doesn't need permission.
+  if (aChannel.EqualsASCII("normal")) {
+    mVolumeChannel = AUDIO_CHANNEL_NORMAL;
+  } else {
+    nsCOMPtr<nsIPermissionManager> permissionManager =
+      do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
+    if (!permissionManager) {
+      return false;
+    }
+    uint32_t perm = nsIPermissionManager::UNKNOWN_ACTION;
+    permissionManager->TestPermissionFromWindow(GetOwner(),
+      nsCString(NS_LITERAL_CSTRING("audio-channel-") +
+      NS_ConvertUTF16toUTF8(aChannel)).get(), &perm);
+    if (perm != nsIPermissionManager::ALLOW_ACTION) {
+      return false;
+    }
+
+    if (aChannel.EqualsASCII("content")) {
+      mVolumeChannel = AUDIO_CHANNEL_CONTENT;
+    } else if (aChannel.EqualsASCII("notification")) {
+      mVolumeChannel = AUDIO_CHANNEL_NOTIFICATION;
+    } else if (aChannel.EqualsASCII("alarm")) {
+      mVolumeChannel = AUDIO_CHANNEL_ALARM;
+    } else if (aChannel.EqualsASCII("telephony")) {
+      mVolumeChannel = AUDIO_CHANNEL_TELEPHONY;
+    } else if (aChannel.EqualsASCII("ringer")) {
+      mVolumeChannel = AUDIO_CHANNEL_RINGER;
+    }
+  }
+
+  if (oldVolumeChannel == mVolumeChannel) {
+    return true;
+  }
+  NotifyVolumeControlChannelChanged();
+  return true;
+}
+
+bool
+AudioChannelManager::GetVolumeControlChannel(nsAString & aChannel)
+{
+  if (mVolumeChannel == AUDIO_CHANNEL_NORMAL) {
+    aChannel.AssignASCII("normal");
+  } else if (mVolumeChannel == AUDIO_CHANNEL_CONTENT) {
+    aChannel.AssignASCII("content");
+  } else if (mVolumeChannel == AUDIO_CHANNEL_NOTIFICATION) {
+    aChannel.AssignASCII("notification");
+  } else if (mVolumeChannel == AUDIO_CHANNEL_ALARM) {
+    aChannel.AssignASCII("alarm");
+  } else if (mVolumeChannel == AUDIO_CHANNEL_TELEPHONY) {
+    aChannel.AssignASCII("telephony");
+  } else if (mVolumeChannel == AUDIO_CHANNEL_RINGER) {
+    aChannel.AssignASCII("ringer");
+  } else {
+    aChannel.AssignASCII("");
+  }
+
+  return true;
+}
+
+void
+AudioChannelManager::NotifyVolumeControlChannelChanged()
+{
+  nsCOMPtr<nsIDocShell> docshell = do_GetInterface(GetOwner());
+  NS_ENSURE_TRUE_VOID(docshell);
+
+  bool isActive = false;
+  docshell->GetIsActive(&isActive);
+
+  AudioChannelService* service = AudioChannelService::GetAudioChannelService();
+  if (isActive) {
+    service->SetDefaultVolumeControlChannel(mVolumeChannel, isActive);
+  } else {
+    service->SetDefaultVolumeControlChannel(AUDIO_CHANNEL_DEFAULT, isActive);
+  }
+}
+
+NS_IMETHODIMP
+AudioChannelManager::HandleEvent(nsIDOMEvent* aEvent)
+{
+  nsAutoString type;
+  aEvent->GetType(type);
+
+  if (type.EqualsLiteral("visibilitychange")) {
+    NotifyVolumeControlChannelChanged();
+  }
+  return NS_OK;
+}
+
 } // namespace system
 } // namespace dom
 } // namespace mozilla
--- a/dom/system/gonk/AudioChannelManager.h
+++ b/dom/system/gonk/AudioChannelManager.h
@@ -3,33 +3,39 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_system_AudioChannelManager_h
 #define mozilla_dom_system_AudioChannelManager_h
 
 #include "mozilla/Hal.h"
 #include "mozilla/HalTypes.h"
 #include "nsDOMEventTargetHelper.h"
+#include "AudioChannelService.h"
 
 namespace mozilla {
 namespace hal {
 class SwitchEvent;
 typedef Observer<SwitchEvent> SwitchObserver;
 } // namespace hal
 
 namespace dom {
 namespace system {
 
-class AudioChannelManager : public nsDOMEventTargetHelper
-                          , public hal::SwitchObserver
+class AudioChannelManager MOZ_FINAL
+  : public nsDOMEventTargetHelper
+  , public hal::SwitchObserver
+  , public nsIDOMEventListener
 {
 public:
   AudioChannelManager();
   virtual ~AudioChannelManager();
 
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_NSIDOMEVENTLISTENER
+
   void Notify(const hal::SwitchEvent& aEvent);
 
   void Init(nsPIDOMWindow* aWindow);
 
   /**
    * WebIDL Interface
    */
 
@@ -42,19 +48,26 @@ public:
                                JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
 
   bool Headphones() const
   {
     MOZ_ASSERT(mState != hal::SWITCH_STATE_UNKNOWN);
     return mState != hal::SWITCH_STATE_OFF;
   }
 
+  bool SetVolumeControlChannel(const nsAString& aChannel);
+
+  bool GetVolumeControlChannel(nsAString& aChannel);
+
   IMPL_EVENT_HANDLER(headphoneschange)
 
 private:
+  void NotifyVolumeControlChannelChanged();
+
   hal::SwitchState mState;
+  AudioChannelType mVolumeChannel;
 };
 
 } // namespace system
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_system_AudioChannelManager_h