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.

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

mercurial