widget/windows/AudioSession.cpp

Thu, 15 Jan 2015 15:59:08 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 15:59:08 +0100
branch
TOR_BUG_9701
changeset 10
ac0c01689b40
permissions
-rw-r--r--

Implement a real Private Browsing Mode condition by changing the API/ABI;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
     2  *
     3  * This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 #include <windows.h>
     8 #include <audiopolicy.h>
     9 #include <mmdeviceapi.h>
    11 #include "nsIStringBundle.h"
    12 #include "nsIUUIDGenerator.h"
    13 #include "nsIXULAppInfo.h"
    15 //#include "AudioSession.h"
    16 #include "nsCOMPtr.h"
    17 #include "nsAutoPtr.h"
    18 #include "nsServiceManagerUtils.h"
    19 #include "nsString.h"
    20 #include "nsThreadUtils.h"
    21 #include "nsXULAppAPI.h"
    22 #include "mozilla/Attributes.h"
    24 #include <objbase.h>
    26 namespace mozilla {
    27 namespace widget {
    29 /* 
    30  * To take advantage of what Vista+ have to offer with respect to audio,
    31  * we need to maintain an audio session.  This class wraps IAudioSessionControl
    32  * and implements IAudioSessionEvents (for callbacks from Windows)
    33  */
    34 class AudioSession MOZ_FINAL : public IAudioSessionEvents {
    35 private:
    36   AudioSession();
    37   ~AudioSession();
    38 public:
    39   static AudioSession* GetSingleton();
    41   // COM IUnknown
    42   STDMETHODIMP_(ULONG) AddRef();
    43   STDMETHODIMP QueryInterface(REFIID, void**);
    44   STDMETHODIMP_(ULONG) Release();
    46   // IAudioSessionEvents
    47   STDMETHODIMP OnChannelVolumeChanged(DWORD aChannelCount,
    48                                       float aChannelVolumeArray[],
    49                                       DWORD aChangedChannel,
    50                                       LPCGUID aContext);
    51   STDMETHODIMP OnDisplayNameChanged(LPCWSTR aDisplayName, LPCGUID aContext);
    52   STDMETHODIMP OnGroupingParamChanged(LPCGUID aGroupingParam, LPCGUID aContext);
    53   STDMETHODIMP OnIconPathChanged(LPCWSTR aIconPath, LPCGUID aContext);
    54   STDMETHODIMP OnSessionDisconnected(AudioSessionDisconnectReason aReason);
    55 private:
    56   nsresult OnSessionDisconnectedInternal();
    57 public:
    58   STDMETHODIMP OnSimpleVolumeChanged(float aVolume,
    59                                      BOOL aMute,
    60                                      LPCGUID aContext);
    61   STDMETHODIMP OnStateChanged(AudioSessionState aState);
    63   nsresult Start();
    64   nsresult Stop();
    65   void StopInternal();
    67   nsresult GetSessionData(nsID& aID,
    68                           nsString& aSessionName,
    69                           nsString& aIconPath);
    71   nsresult SetSessionData(const nsID& aID,
    72                           const nsString& aSessionName,
    73                           const nsString& aIconPath);
    75   enum SessionState {
    76     UNINITIALIZED, // Has not been initialized yet
    77     STARTED, // Started
    78     CLONED, // SetSessionInfoCalled, Start not called
    79     FAILED, // The autdio session failed to start
    80     STOPPED, // Stop called
    81     AUDIO_SESSION_DISCONNECTED // Audio session disconnected
    82   };
    83 protected:
    84   nsRefPtr<IAudioSessionControl> mAudioSessionControl;
    85   nsString mDisplayName;
    86   nsString mIconPath;
    87   nsID mSessionGroupingParameter;
    88   SessionState mState;
    90   ThreadSafeAutoRefCnt mRefCnt;
    91   NS_DECL_OWNINGTHREAD
    93   static AudioSession* sService;
    94 };
    96 nsresult
    97 StartAudioSession()
    98 {
    99   return AudioSession::GetSingleton()->Start();
   100 }
   102 nsresult
   103 StopAudioSession()
   104 {
   105   return AudioSession::GetSingleton()->Stop();
   106 }
   108 nsresult
   109 GetAudioSessionData(nsID& aID,
   110                     nsString& aSessionName,
   111                     nsString& aIconPath)
   112 {
   113   return AudioSession::GetSingleton()->GetSessionData(aID,
   114                                                       aSessionName,
   115                                                       aIconPath);
   116 }
   118 nsresult
   119 RecvAudioSessionData(const nsID& aID,
   120                      const nsString& aSessionName,
   121                      const nsString& aIconPath)
   122 {
   123   return AudioSession::GetSingleton()->SetSessionData(aID,
   124                                                       aSessionName,
   125                                                       aIconPath);
   126 }
   128 AudioSession* AudioSession::sService = nullptr;
   130 AudioSession::AudioSession()
   131 {
   132   mState = UNINITIALIZED;
   133 }
   135 AudioSession::~AudioSession()
   136 {
   138 }
   140 AudioSession*
   141 AudioSession::GetSingleton()
   142 {
   143   if (!(AudioSession::sService)) {
   144     nsRefPtr<AudioSession> service = new AudioSession();
   145     service.forget(&AudioSession::sService);
   146   }
   148   // We don't refcount AudioSession on the Gecko side, we hold one single ref
   149   // as long as the appshell is running.
   150   return AudioSession::sService;
   151 }
   153 // It appears Windows will use us on a background thread ...
   154 NS_IMPL_ADDREF(AudioSession)
   155 NS_IMPL_RELEASE(AudioSession)
   157 STDMETHODIMP
   158 AudioSession::QueryInterface(REFIID iid, void **ppv)
   159 {
   160   const IID IID_IAudioSessionEvents = __uuidof(IAudioSessionEvents);
   161   if ((IID_IUnknown == iid) ||
   162       (IID_IAudioSessionEvents == iid)) {
   163     *ppv = static_cast<IAudioSessionEvents*>(this);
   164     AddRef();
   165     return S_OK;
   166   }
   168   return E_NOINTERFACE;
   169 }
   171 // Once we are started Windows will hold a reference to us through our
   172 // IAudioSessionEvents interface that will keep us alive until the appshell
   173 // calls Stop.
   174 nsresult
   175 AudioSession::Start()
   176 {
   177   NS_ABORT_IF_FALSE(mState == UNINITIALIZED || 
   178                     mState == CLONED ||
   179                     mState == AUDIO_SESSION_DISCONNECTED,
   180                     "State invariants violated");
   182   const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
   183   const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
   184   const IID IID_IAudioSessionManager = __uuidof(IAudioSessionManager);
   186   HRESULT hr;
   188   // Don't check for errors in case something already initialized COM
   189   // on this thread.
   190   CoInitialize(nullptr);
   192   if (mState == UNINITIALIZED) {
   193     mState = FAILED;
   195     // XXXkhuey implement this for content processes
   196     if (XRE_GetProcessType() == GeckoProcessType_Content)
   197       return NS_ERROR_FAILURE;
   199     NS_ABORT_IF_FALSE(XRE_GetProcessType() == GeckoProcessType_Default,
   200                       "Should only get here in a chrome process!");
   202     nsCOMPtr<nsIStringBundleService> bundleService = 
   203       do_GetService(NS_STRINGBUNDLE_CONTRACTID);
   204     NS_ENSURE_TRUE(bundleService, NS_ERROR_FAILURE);
   205     nsCOMPtr<nsIStringBundle> bundle;
   206     bundleService->CreateBundle("chrome://branding/locale/brand.properties",
   207                                 getter_AddRefs(bundle));
   208     NS_ENSURE_TRUE(bundle, NS_ERROR_FAILURE);
   210     bundle->GetStringFromName(MOZ_UTF16("brandFullName"),
   211                               getter_Copies(mDisplayName));
   213     wchar_t *buffer;
   214     mIconPath.GetMutableData(&buffer, MAX_PATH);
   216     // XXXkhuey we should provide a way for a xulrunner app to specify an icon
   217     // that's not in the product binary.
   218     ::GetModuleFileNameW(nullptr, buffer, MAX_PATH);
   220     nsCOMPtr<nsIUUIDGenerator> uuidgen =
   221       do_GetService("@mozilla.org/uuid-generator;1");
   222     NS_ENSURE_TRUE(uuidgen, NS_ERROR_FAILURE);
   223     uuidgen->GenerateUUIDInPlace(&mSessionGroupingParameter);
   224   }
   226   mState = FAILED;
   228   NS_ABORT_IF_FALSE(!mDisplayName.IsEmpty() || !mIconPath.IsEmpty(),
   229                     "Should never happen ...");
   231   nsRefPtr<IMMDeviceEnumerator> enumerator;
   232   hr = ::CoCreateInstance(CLSID_MMDeviceEnumerator,
   233                           nullptr,
   234                           CLSCTX_ALL,
   235                           IID_IMMDeviceEnumerator,
   236                           getter_AddRefs(enumerator));
   237   if (FAILED(hr))
   238     return NS_ERROR_NOT_AVAILABLE;
   240   nsRefPtr<IMMDevice> device;
   241   hr = enumerator->GetDefaultAudioEndpoint(EDataFlow::eRender,
   242                                            ERole::eMultimedia,
   243                                            getter_AddRefs(device));
   244   if (FAILED(hr)) {
   245     if (hr == E_NOTFOUND)
   246       return NS_ERROR_NOT_AVAILABLE;
   247     return NS_ERROR_FAILURE;
   248   }
   250   nsRefPtr<IAudioSessionManager> manager;
   251   hr = device->Activate(IID_IAudioSessionManager,
   252                         CLSCTX_ALL,
   253                         nullptr,
   254                         getter_AddRefs(manager));
   255   if (FAILED(hr))
   256     return NS_ERROR_FAILURE;
   258   hr = manager->GetAudioSessionControl(nullptr,
   259                                        FALSE,
   260                                        getter_AddRefs(mAudioSessionControl));
   261   if (FAILED(hr))
   262     return NS_ERROR_FAILURE;
   264   hr = mAudioSessionControl->SetGroupingParam((LPCGUID)&mSessionGroupingParameter,
   265                                               nullptr);
   266   if (FAILED(hr)) {
   267     StopInternal();
   268     return NS_ERROR_FAILURE;
   269   }
   271   hr = mAudioSessionControl->SetDisplayName(mDisplayName.get(), nullptr);
   272   if (FAILED(hr)) {
   273     StopInternal();
   274     return NS_ERROR_FAILURE;
   275   }
   277   hr = mAudioSessionControl->SetIconPath(mIconPath.get(), nullptr);
   278   if (FAILED(hr)) {
   279     StopInternal();
   280     return NS_ERROR_FAILURE;
   281   }
   283   hr = mAudioSessionControl->RegisterAudioSessionNotification(this);
   284   if (FAILED(hr)) {
   285     StopInternal();
   286     return NS_ERROR_FAILURE;
   287   }
   289   mState = STARTED;
   291   return NS_OK;
   292 }
   294 void
   295 AudioSession::StopInternal()
   296 {
   297   static const nsID blankId = {0, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0} };
   299   if (mAudioSessionControl) {
   300     mAudioSessionControl->SetGroupingParam((LPCGUID)&blankId, nullptr);
   301     mAudioSessionControl->UnregisterAudioSessionNotification(this);
   302     mAudioSessionControl = nullptr;
   303   }
   304 }
   306 nsresult
   307 AudioSession::Stop()
   308 {
   309   NS_ABORT_IF_FALSE(mState == STARTED ||
   310                     mState == UNINITIALIZED || // XXXremove this
   311                     mState == FAILED,
   312                     "State invariants violated");
   313   mState = STOPPED;
   315   nsRefPtr<AudioSession> kungFuDeathGrip;
   316   kungFuDeathGrip.swap(sService);
   318   if (XRE_GetProcessType() != GeckoProcessType_Content)
   319     StopInternal();
   321   // At this point kungFuDeathGrip should be the only reference to AudioSession
   323   ::CoUninitialize();
   325   return NS_OK;
   326 }
   328 void CopynsID(nsID& lhs, const nsID& rhs)
   329 {
   330   lhs.m0 = rhs.m0;
   331   lhs.m1 = rhs.m1;
   332   lhs.m2 = rhs.m2;
   333   for (int i = 0; i < 8; i++ ) {
   334     lhs.m3[i] = rhs.m3[i];
   335   }
   336 }
   338 nsresult
   339 AudioSession::GetSessionData(nsID& aID,
   340                              nsString& aSessionName,
   341                              nsString& aIconPath)
   342 {
   343   NS_ABORT_IF_FALSE(mState == FAILED ||
   344                     mState == STARTED ||
   345                     mState == CLONED,
   346                     "State invariants violated");
   348   CopynsID(aID, mSessionGroupingParameter);
   349   aSessionName = mDisplayName;
   350   aIconPath = mIconPath;
   352   if (mState == FAILED)
   353     return NS_ERROR_FAILURE;
   355   return NS_OK;
   356 }
   358 nsresult
   359 AudioSession::SetSessionData(const nsID& aID,
   360                              const nsString& aSessionName,
   361                              const nsString& aIconPath)
   362 {
   363   NS_ABORT_IF_FALSE(mState == UNINITIALIZED,
   364                     "State invariants violated");
   365   NS_ABORT_IF_FALSE(XRE_GetProcessType() != GeckoProcessType_Default,
   366                     "Should never get here in a chrome process!");
   367   mState = CLONED;
   369   CopynsID(mSessionGroupingParameter, aID);
   370   mDisplayName = aSessionName;
   371   mIconPath = aIconPath;
   372   return NS_OK;
   373 }
   375 STDMETHODIMP
   376 AudioSession::OnChannelVolumeChanged(DWORD aChannelCount,
   377                                      float aChannelVolumeArray[],
   378                                      DWORD aChangedChannel,
   379                                      LPCGUID aContext)
   380 {
   381   return S_OK; // NOOP
   382 }
   384 STDMETHODIMP
   385 AudioSession::OnDisplayNameChanged(LPCWSTR aDisplayName,
   386                                    LPCGUID aContext)
   387 {
   388   return S_OK; // NOOP
   389 }
   391 STDMETHODIMP
   392 AudioSession::OnGroupingParamChanged(LPCGUID aGroupingParam,
   393                                      LPCGUID aContext)
   394 {
   395   return S_OK; // NOOP
   396 }
   398 STDMETHODIMP
   399 AudioSession::OnIconPathChanged(LPCWSTR aIconPath,
   400                                 LPCGUID aContext)
   401 {
   402   return S_OK; // NOOP
   403 }
   405 STDMETHODIMP
   406 AudioSession::OnSessionDisconnected(AudioSessionDisconnectReason aReason)
   407 {
   408   // Run our code asynchronously.  Per MSDN we can't do anything interesting
   409   // in this callback.
   410   nsCOMPtr<nsIRunnable> runnable =
   411     NS_NewRunnableMethod(this, &AudioSession::OnSessionDisconnectedInternal);
   412   NS_DispatchToMainThread(runnable);
   413   return S_OK;
   414 }
   416 nsresult
   417 AudioSession::OnSessionDisconnectedInternal()
   418 {
   419   if (!mAudioSessionControl)
   420     return NS_OK;
   422   mAudioSessionControl->UnregisterAudioSessionNotification(this);
   423   mAudioSessionControl = nullptr;
   425   mState = AUDIO_SESSION_DISCONNECTED;
   426   CoUninitialize();
   427   Start(); // If it fails there's not much we can do.
   428   return NS_OK;
   429 }
   431 STDMETHODIMP
   432 AudioSession::OnSimpleVolumeChanged(float aVolume,
   433                                     BOOL aMute,
   434                                     LPCGUID aContext)
   435 {
   436   return S_OK; // NOOP
   437 }
   439 STDMETHODIMP
   440 AudioSession::OnStateChanged(AudioSessionState aState)
   441 {
   442   return S_OK; // NOOP
   443 }
   445 } // namespace widget
   446 } // namespace mozilla

mercurial