dom/audiochannel/AudioChannelService.cpp

Thu, 15 Jan 2015 15:55:04 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 15:55:04 +0100
branch
TOR_BUG_9701
changeset 9
a63d609f5ebe
permissions
-rw-r--r--

Back out 97036ab72558 which inappropriately compared turds to third parties.

michael@0 1 /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
michael@0 2 /* vim: set ts=2 et sw=2 tw=80: */
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 file,
michael@0 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 #include "AudioChannelService.h"
michael@0 8 #include "AudioChannelServiceChild.h"
michael@0 9
michael@0 10 #include "base/basictypes.h"
michael@0 11
michael@0 12 #include "mozilla/Services.h"
michael@0 13 #include "mozilla/StaticPtr.h"
michael@0 14 #include "mozilla/unused.h"
michael@0 15
michael@0 16 #include "mozilla/dom/ContentParent.h"
michael@0 17
michael@0 18 #include "nsThreadUtils.h"
michael@0 19 #include "nsHashPropertyBag.h"
michael@0 20 #include "nsComponentManagerUtils.h"
michael@0 21 #include "nsPIDOMWindow.h"
michael@0 22 #include "nsServiceManagerUtils.h"
michael@0 23
michael@0 24 #ifdef MOZ_WIDGET_GONK
michael@0 25 #include "nsJSUtils.h"
michael@0 26 #include "nsCxPusher.h"
michael@0 27 #include "nsIAudioManager.h"
michael@0 28 #include "SpeakerManagerService.h"
michael@0 29 #define NS_AUDIOMANAGER_CONTRACTID "@mozilla.org/telephony/audiomanager;1"
michael@0 30 #endif
michael@0 31
michael@0 32 #include "mozilla/Preferences.h"
michael@0 33
michael@0 34 using namespace mozilla;
michael@0 35 using namespace mozilla::dom;
michael@0 36 using namespace mozilla::hal;
michael@0 37
michael@0 38 StaticRefPtr<AudioChannelService> gAudioChannelService;
michael@0 39
michael@0 40 // Mappings from 'mozaudiochannel' attribute strings to an enumeration.
michael@0 41 static const nsAttrValue::EnumTable kMozAudioChannelAttributeTable[] = {
michael@0 42 { "normal", (int16_t)AudioChannel::Normal },
michael@0 43 { "content", (int16_t)AudioChannel::Content },
michael@0 44 { "notification", (int16_t)AudioChannel::Notification },
michael@0 45 { "alarm", (int16_t)AudioChannel::Alarm },
michael@0 46 { "telephony", (int16_t)AudioChannel::Telephony },
michael@0 47 { "ringer", (int16_t)AudioChannel::Ringer },
michael@0 48 { "publicnotification", (int16_t)AudioChannel::Publicnotification },
michael@0 49 { nullptr }
michael@0 50 };
michael@0 51
michael@0 52 // static
michael@0 53 AudioChannelService*
michael@0 54 AudioChannelService::GetAudioChannelService()
michael@0 55 {
michael@0 56 MOZ_ASSERT(NS_IsMainThread());
michael@0 57
michael@0 58 if (XRE_GetProcessType() != GeckoProcessType_Default) {
michael@0 59 return AudioChannelServiceChild::GetAudioChannelService();
michael@0 60 }
michael@0 61
michael@0 62 // If we already exist, exit early
michael@0 63 if (gAudioChannelService) {
michael@0 64 return gAudioChannelService;
michael@0 65 }
michael@0 66
michael@0 67 // Create new instance, register, return
michael@0 68 nsRefPtr<AudioChannelService> service = new AudioChannelService();
michael@0 69 NS_ENSURE_TRUE(service, nullptr);
michael@0 70
michael@0 71 gAudioChannelService = service;
michael@0 72 return gAudioChannelService;
michael@0 73 }
michael@0 74
michael@0 75 void
michael@0 76 AudioChannelService::Shutdown()
michael@0 77 {
michael@0 78 if (XRE_GetProcessType() != GeckoProcessType_Default) {
michael@0 79 return AudioChannelServiceChild::Shutdown();
michael@0 80 }
michael@0 81
michael@0 82 if (gAudioChannelService) {
michael@0 83 gAudioChannelService = nullptr;
michael@0 84 }
michael@0 85 }
michael@0 86
michael@0 87 NS_IMPL_ISUPPORTS(AudioChannelService, nsIObserver, nsITimerCallback)
michael@0 88
michael@0 89 AudioChannelService::AudioChannelService()
michael@0 90 : mCurrentHigherChannel(-1)
michael@0 91 , mCurrentVisibleHigherChannel(-1)
michael@0 92 , mPlayableHiddenContentChildID(CONTENT_PROCESS_ID_UNKNOWN)
michael@0 93 , mDisabled(false)
michael@0 94 , mDefChannelChildID(CONTENT_PROCESS_ID_UNKNOWN)
michael@0 95 {
michael@0 96 if (XRE_GetProcessType() == GeckoProcessType_Default) {
michael@0 97 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
michael@0 98 if (obs) {
michael@0 99 obs->AddObserver(this, "ipc:content-shutdown", false);
michael@0 100 obs->AddObserver(this, "xpcom-shutdown", false);
michael@0 101 #ifdef MOZ_WIDGET_GONK
michael@0 102 // To monitor the volume settings based on audio channel.
michael@0 103 obs->AddObserver(this, "mozsettings-changed", false);
michael@0 104 #endif
michael@0 105 }
michael@0 106 }
michael@0 107 }
michael@0 108
michael@0 109 AudioChannelService::~AudioChannelService()
michael@0 110 {
michael@0 111 }
michael@0 112
michael@0 113 void
michael@0 114 AudioChannelService::RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
michael@0 115 AudioChannel aChannel,
michael@0 116 bool aWithVideo)
michael@0 117 {
michael@0 118 if (mDisabled) {
michael@0 119 return;
michael@0 120 }
michael@0 121
michael@0 122 AudioChannelAgentData* data = new AudioChannelAgentData(aChannel,
michael@0 123 true /* aElementHidden */,
michael@0 124 AUDIO_CHANNEL_STATE_MUTED /* aState */,
michael@0 125 aWithVideo);
michael@0 126 mAgents.Put(aAgent, data);
michael@0 127 RegisterType(aChannel, CONTENT_PROCESS_ID_MAIN, aWithVideo);
michael@0 128
michael@0 129 // If this is the first agent for this window, we must notify the observers.
michael@0 130 uint32_t count = CountWindow(aAgent->Window());
michael@0 131 if (count == 1) {
michael@0 132 nsCOMPtr<nsIObserverService> observerService =
michael@0 133 services::GetObserverService();
michael@0 134 if (observerService) {
michael@0 135 observerService->NotifyObservers(ToSupports(aAgent->Window()),
michael@0 136 "media-playback",
michael@0 137 NS_LITERAL_STRING("active").get());
michael@0 138 }
michael@0 139 }
michael@0 140 }
michael@0 141
michael@0 142 void
michael@0 143 AudioChannelService::RegisterType(AudioChannel aChannel, uint64_t aChildID,
michael@0 144 bool aWithVideo)
michael@0 145 {
michael@0 146 if (mDisabled) {
michael@0 147 return;
michael@0 148 }
michael@0 149
michael@0 150 AudioChannelInternalType type = GetInternalType(aChannel, true);
michael@0 151 mChannelCounters[type].AppendElement(aChildID);
michael@0 152
michael@0 153 if (XRE_GetProcessType() == GeckoProcessType_Default) {
michael@0 154 // Since there is another telephony registered, we can unregister old one
michael@0 155 // immediately.
michael@0 156 if (mDeferTelChannelTimer && aChannel == AudioChannel::Telephony) {
michael@0 157 mDeferTelChannelTimer->Cancel();
michael@0 158 mDeferTelChannelTimer = nullptr;
michael@0 159 UnregisterTypeInternal(aChannel, mTimerElementHidden, mTimerChildID,
michael@0 160 false);
michael@0 161 }
michael@0 162
michael@0 163 if (aWithVideo) {
michael@0 164 mWithVideoChildIDs.AppendElement(aChildID);
michael@0 165 }
michael@0 166
michael@0 167 // No hidden content channel can be playable if there is a content channel
michael@0 168 // in foreground (bug 855208), nor if there is a normal channel with video
michael@0 169 // in foreground (bug 894249).
michael@0 170 if (type == AUDIO_CHANNEL_INT_CONTENT ||
michael@0 171 (type == AUDIO_CHANNEL_INT_NORMAL &&
michael@0 172 mWithVideoChildIDs.Contains(aChildID))) {
michael@0 173 mPlayableHiddenContentChildID = CONTENT_PROCESS_ID_UNKNOWN;
michael@0 174 }
michael@0 175 // One hidden content channel can be playable only when there is no any
michael@0 176 // content channel in the foreground, and no normal channel with video in
michael@0 177 // foreground.
michael@0 178 else if (type == AUDIO_CHANNEL_INT_CONTENT_HIDDEN &&
michael@0 179 mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty()) {
michael@0 180 mPlayableHiddenContentChildID = aChildID;
michael@0 181 }
michael@0 182
michael@0 183 // In order to avoid race conditions, it's safer to notify any existing
michael@0 184 // agent any time a new one is registered.
michael@0 185 SendAudioChannelChangedNotification(aChildID);
michael@0 186 Notify();
michael@0 187 }
michael@0 188 }
michael@0 189
michael@0 190 void
michael@0 191 AudioChannelService::UnregisterAudioChannelAgent(AudioChannelAgent* aAgent)
michael@0 192 {
michael@0 193 if (mDisabled) {
michael@0 194 return;
michael@0 195 }
michael@0 196
michael@0 197 nsAutoPtr<AudioChannelAgentData> data;
michael@0 198 mAgents.RemoveAndForget(aAgent, data);
michael@0 199
michael@0 200 if (data) {
michael@0 201 UnregisterType(data->mChannel, data->mElementHidden,
michael@0 202 CONTENT_PROCESS_ID_MAIN, data->mWithVideo);
michael@0 203 }
michael@0 204 #ifdef MOZ_WIDGET_GONK
michael@0 205 bool active = AnyAudioChannelIsActive();
michael@0 206 for (uint32_t i = 0; i < mSpeakerManager.Length(); i++) {
michael@0 207 mSpeakerManager[i]->SetAudioChannelActive(active);
michael@0 208 }
michael@0 209 #endif
michael@0 210
michael@0 211 // If this is the last agent for this window, we must notify the observers.
michael@0 212 uint32_t count = CountWindow(aAgent->Window());
michael@0 213 if (count == 0) {
michael@0 214 nsCOMPtr<nsIObserverService> observerService =
michael@0 215 services::GetObserverService();
michael@0 216 if (observerService) {
michael@0 217 observerService->NotifyObservers(ToSupports(aAgent->Window()),
michael@0 218 "media-playback",
michael@0 219 NS_LITERAL_STRING("inactive").get());
michael@0 220 }
michael@0 221 }
michael@0 222 }
michael@0 223
michael@0 224 void
michael@0 225 AudioChannelService::UnregisterType(AudioChannel aChannel,
michael@0 226 bool aElementHidden,
michael@0 227 uint64_t aChildID,
michael@0 228 bool aWithVideo)
michael@0 229 {
michael@0 230 if (mDisabled) {
michael@0 231 return;
michael@0 232 }
michael@0 233
michael@0 234 // There are two reasons to defer the decrease of telephony channel.
michael@0 235 // 1. User can have time to remove device from his ear before music resuming.
michael@0 236 // 2. Give BT SCO to be disconnected before starting to connect A2DP.
michael@0 237 if (XRE_GetProcessType() == GeckoProcessType_Default &&
michael@0 238 aChannel == AudioChannel::Telephony &&
michael@0 239 (mChannelCounters[AUDIO_CHANNEL_INT_TELEPHONY_HIDDEN].Length() +
michael@0 240 mChannelCounters[AUDIO_CHANNEL_INT_TELEPHONY].Length()) == 1) {
michael@0 241 mTimerElementHidden = aElementHidden;
michael@0 242 mTimerChildID = aChildID;
michael@0 243 mDeferTelChannelTimer = do_CreateInstance("@mozilla.org/timer;1");
michael@0 244 mDeferTelChannelTimer->InitWithCallback(this, 1500, nsITimer::TYPE_ONE_SHOT);
michael@0 245 return;
michael@0 246 }
michael@0 247
michael@0 248 UnregisterTypeInternal(aChannel, aElementHidden, aChildID, aWithVideo);
michael@0 249 }
michael@0 250
michael@0 251 void
michael@0 252 AudioChannelService::UnregisterTypeInternal(AudioChannel aChannel,
michael@0 253 bool aElementHidden,
michael@0 254 uint64_t aChildID,
michael@0 255 bool aWithVideo)
michael@0 256 {
michael@0 257 // The array may contain multiple occurrence of this appId but
michael@0 258 // this should remove only the first one.
michael@0 259 AudioChannelInternalType type = GetInternalType(aChannel, aElementHidden);
michael@0 260 MOZ_ASSERT(mChannelCounters[type].Contains(aChildID));
michael@0 261 mChannelCounters[type].RemoveElement(aChildID);
michael@0 262
michael@0 263 // In order to avoid race conditions, it's safer to notify any existing
michael@0 264 // agent any time a new one is registered.
michael@0 265 if (XRE_GetProcessType() == GeckoProcessType_Default) {
michael@0 266 // No hidden content channel is playable if the original playable hidden
michael@0 267 // process does not need to play audio from background anymore.
michael@0 268 if (aChannel == AudioChannel::Content &&
michael@0 269 mPlayableHiddenContentChildID == aChildID &&
michael@0 270 !mChannelCounters[AUDIO_CHANNEL_INT_CONTENT_HIDDEN].Contains(aChildID)) {
michael@0 271 mPlayableHiddenContentChildID = CONTENT_PROCESS_ID_UNKNOWN;
michael@0 272 }
michael@0 273
michael@0 274 if (aWithVideo) {
michael@0 275 MOZ_ASSERT(mWithVideoChildIDs.Contains(aChildID));
michael@0 276 mWithVideoChildIDs.RemoveElement(aChildID);
michael@0 277 }
michael@0 278
michael@0 279 SendAudioChannelChangedNotification(aChildID);
michael@0 280 Notify();
michael@0 281 }
michael@0 282 }
michael@0 283
michael@0 284 void
michael@0 285 AudioChannelService::UpdateChannelType(AudioChannel aChannel,
michael@0 286 uint64_t aChildID,
michael@0 287 bool aElementHidden,
michael@0 288 bool aElementWasHidden)
michael@0 289 {
michael@0 290 // Calculate the new and old internal type and update the hashtable if needed.
michael@0 291 AudioChannelInternalType newType = GetInternalType(aChannel, aElementHidden);
michael@0 292 AudioChannelInternalType oldType = GetInternalType(aChannel, aElementWasHidden);
michael@0 293
michael@0 294 if (newType != oldType) {
michael@0 295 mChannelCounters[newType].AppendElement(aChildID);
michael@0 296 MOZ_ASSERT(mChannelCounters[oldType].Contains(aChildID));
michael@0 297 mChannelCounters[oldType].RemoveElement(aChildID);
michael@0 298 }
michael@0 299
michael@0 300 // No hidden content channel can be playable if there is a content channel
michael@0 301 // in foreground (bug 855208), nor if there is a normal channel with video
michael@0 302 // in foreground (bug 894249).
michael@0 303 if (newType == AUDIO_CHANNEL_INT_CONTENT ||
michael@0 304 (newType == AUDIO_CHANNEL_INT_NORMAL &&
michael@0 305 mWithVideoChildIDs.Contains(aChildID))) {
michael@0 306 mPlayableHiddenContentChildID = CONTENT_PROCESS_ID_UNKNOWN;
michael@0 307 }
michael@0 308 // If there is no content channel in foreground and no normal channel with
michael@0 309 // video in foreground, the last content channel which goes from foreground
michael@0 310 // to background can be playable.
michael@0 311 else if (oldType == AUDIO_CHANNEL_INT_CONTENT &&
michael@0 312 newType == AUDIO_CHANNEL_INT_CONTENT_HIDDEN &&
michael@0 313 mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty()) {
michael@0 314 mPlayableHiddenContentChildID = aChildID;
michael@0 315 }
michael@0 316 }
michael@0 317
michael@0 318 AudioChannelState
michael@0 319 AudioChannelService::GetState(AudioChannelAgent* aAgent, bool aElementHidden)
michael@0 320 {
michael@0 321 AudioChannelAgentData* data;
michael@0 322 if (!mAgents.Get(aAgent, &data)) {
michael@0 323 return AUDIO_CHANNEL_STATE_MUTED;
michael@0 324 }
michael@0 325
michael@0 326 bool oldElementHidden = data->mElementHidden;
michael@0 327 // Update visibility.
michael@0 328 data->mElementHidden = aElementHidden;
michael@0 329
michael@0 330 data->mState = GetStateInternal(data->mChannel, CONTENT_PROCESS_ID_MAIN,
michael@0 331 aElementHidden, oldElementHidden);
michael@0 332 return data->mState;
michael@0 333 }
michael@0 334
michael@0 335 AudioChannelState
michael@0 336 AudioChannelService::GetStateInternal(AudioChannel aChannel, uint64_t aChildID,
michael@0 337 bool aElementHidden,
michael@0 338 bool aElementWasHidden)
michael@0 339 {
michael@0 340 UpdateChannelType(aChannel, aChildID, aElementHidden, aElementWasHidden);
michael@0 341
michael@0 342 // Calculating the new and old type and update the hashtable if needed.
michael@0 343 AudioChannelInternalType newType = GetInternalType(aChannel, aElementHidden);
michael@0 344 AudioChannelInternalType oldType = GetInternalType(aChannel,
michael@0 345 aElementWasHidden);
michael@0 346
michael@0 347 if (newType != oldType &&
michael@0 348 (aChannel == AudioChannel::Content ||
michael@0 349 (aChannel == AudioChannel::Normal &&
michael@0 350 mWithVideoChildIDs.Contains(aChildID)))) {
michael@0 351 Notify();
michael@0 352 }
michael@0 353
michael@0 354 SendAudioChannelChangedNotification(aChildID);
michael@0 355
michael@0 356 // Let play any visible audio channel.
michael@0 357 if (!aElementHidden) {
michael@0 358 if (CheckVolumeFadedCondition(newType, aElementHidden)) {
michael@0 359 return AUDIO_CHANNEL_STATE_FADED;
michael@0 360 }
michael@0 361 return AUDIO_CHANNEL_STATE_NORMAL;
michael@0 362 }
michael@0 363
michael@0 364 // We are not visible, maybe we have to mute.
michael@0 365 if (newType == AUDIO_CHANNEL_INT_NORMAL_HIDDEN ||
michael@0 366 (newType == AUDIO_CHANNEL_INT_CONTENT_HIDDEN &&
michael@0 367 // One process can have multiple content channels; and during the
michael@0 368 // transition from foreground to background, its content channels will be
michael@0 369 // updated with correct visibility status one by one. All its content
michael@0 370 // channels should remain playable until all of their visibility statuses
michael@0 371 // have been updated as hidden. After all its content channels have been
michael@0 372 // updated properly as hidden, mPlayableHiddenContentChildID is used to
michael@0 373 // check whether this background process is playable or not.
michael@0 374 !(mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].Contains(aChildID) ||
michael@0 375 (mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty() &&
michael@0 376 mPlayableHiddenContentChildID == aChildID)))) {
michael@0 377 return AUDIO_CHANNEL_STATE_MUTED;
michael@0 378 }
michael@0 379
michael@0 380 // After checking the condition on normal & content channel, if the state
michael@0 381 // is not on muted then checking other higher channels type here.
michael@0 382 if (ChannelsActiveWithHigherPriorityThan(newType)) {
michael@0 383 MOZ_ASSERT(newType != AUDIO_CHANNEL_INT_NORMAL_HIDDEN);
michael@0 384 if (CheckVolumeFadedCondition(newType, aElementHidden)) {
michael@0 385 return AUDIO_CHANNEL_STATE_FADED;
michael@0 386 }
michael@0 387 return AUDIO_CHANNEL_STATE_MUTED;
michael@0 388 }
michael@0 389
michael@0 390 return AUDIO_CHANNEL_STATE_NORMAL;
michael@0 391 }
michael@0 392
michael@0 393 bool
michael@0 394 AudioChannelService::CheckVolumeFadedCondition(AudioChannelInternalType aType,
michael@0 395 bool aElementHidden)
michael@0 396 {
michael@0 397 // Only normal & content channels are considered
michael@0 398 if (aType > AUDIO_CHANNEL_INT_CONTENT_HIDDEN) {
michael@0 399 return false;
michael@0 400 }
michael@0 401
michael@0 402 // Consider that audio from notification is with short duration
michael@0 403 // so just fade the volume not pause it
michael@0 404 if (mChannelCounters[AUDIO_CHANNEL_INT_NOTIFICATION].IsEmpty() &&
michael@0 405 mChannelCounters[AUDIO_CHANNEL_INT_NOTIFICATION_HIDDEN].IsEmpty()) {
michael@0 406 return false;
michael@0 407 }
michael@0 408
michael@0 409 // Since this element is on the foreground, it can be allowed to play always.
michael@0 410 // So return true directly when there is any notification channel alive.
michael@0 411 if (aElementHidden == false) {
michael@0 412 return true;
michael@0 413 }
michael@0 414
michael@0 415 // If element is on the background, it is possible paused by channels higher
michael@0 416 // then notification.
michael@0 417 for (int i = AUDIO_CHANNEL_INT_LAST - 1;
michael@0 418 i != AUDIO_CHANNEL_INT_NOTIFICATION_HIDDEN; --i) {
michael@0 419 if (!mChannelCounters[i].IsEmpty()) {
michael@0 420 return false;
michael@0 421 }
michael@0 422 }
michael@0 423
michael@0 424 return true;
michael@0 425 }
michael@0 426
michael@0 427 bool
michael@0 428 AudioChannelService::ContentOrNormalChannelIsActive()
michael@0 429 {
michael@0 430 return !mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty() ||
michael@0 431 !mChannelCounters[AUDIO_CHANNEL_INT_CONTENT_HIDDEN].IsEmpty() ||
michael@0 432 !mChannelCounters[AUDIO_CHANNEL_INT_NORMAL].IsEmpty();
michael@0 433 }
michael@0 434
michael@0 435 bool
michael@0 436 AudioChannelService::ProcessContentOrNormalChannelIsActive(uint64_t aChildID)
michael@0 437 {
michael@0 438 return mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].Contains(aChildID) ||
michael@0 439 mChannelCounters[AUDIO_CHANNEL_INT_CONTENT_HIDDEN].Contains(aChildID) ||
michael@0 440 mChannelCounters[AUDIO_CHANNEL_INT_NORMAL].Contains(aChildID);
michael@0 441 }
michael@0 442
michael@0 443 void
michael@0 444 AudioChannelService::SetDefaultVolumeControlChannel(int32_t aChannel,
michael@0 445 bool aHidden)
michael@0 446 {
michael@0 447 SetDefaultVolumeControlChannelInternal(aChannel, aHidden,
michael@0 448 CONTENT_PROCESS_ID_MAIN);
michael@0 449 }
michael@0 450
michael@0 451 void
michael@0 452 AudioChannelService::SetDefaultVolumeControlChannelInternal(int32_t aChannel,
michael@0 453 bool aHidden,
michael@0 454 uint64_t aChildID)
michael@0 455 {
michael@0 456 if (XRE_GetProcessType() != GeckoProcessType_Default) {
michael@0 457 return;
michael@0 458 }
michael@0 459
michael@0 460 // If this child is in the background and mDefChannelChildID is set to
michael@0 461 // others then it means other child in the foreground already set it's
michael@0 462 // own default channel already.
michael@0 463 if (!aHidden && mDefChannelChildID != aChildID) {
michael@0 464 return;
michael@0 465 }
michael@0 466
michael@0 467 mDefChannelChildID = aChildID;
michael@0 468 nsString channelName;
michael@0 469
michael@0 470 if (aChannel == -1) {
michael@0 471 channelName.AssignASCII("unknown");
michael@0 472 } else {
michael@0 473 GetAudioChannelString(static_cast<AudioChannel>(aChannel), channelName);
michael@0 474 }
michael@0 475
michael@0 476 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
michael@0 477 if (obs) {
michael@0 478 obs->NotifyObservers(nullptr, "default-volume-channel-changed",
michael@0 479 channelName.get());
michael@0 480 }
michael@0 481 }
michael@0 482
michael@0 483 void
michael@0 484 AudioChannelService::SendAudioChannelChangedNotification(uint64_t aChildID)
michael@0 485 {
michael@0 486 if (XRE_GetProcessType() != GeckoProcessType_Default) {
michael@0 487 return;
michael@0 488 }
michael@0 489
michael@0 490 nsRefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
michael@0 491 props->SetPropertyAsUint64(NS_LITERAL_STRING("childID"), aChildID);
michael@0 492
michael@0 493 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
michael@0 494 if (obs) {
michael@0 495 obs->NotifyObservers(static_cast<nsIWritablePropertyBag*>(props),
michael@0 496 "audio-channel-process-changed", nullptr);
michael@0 497 }
michael@0 498
michael@0 499 // Calculating the most important active channel.
michael@0 500 int32_t higher = -1;
michael@0 501
michael@0 502 // Top-Down in the hierarchy for visible elements
michael@0 503 if (!mChannelCounters[AUDIO_CHANNEL_INT_PUBLICNOTIFICATION].IsEmpty()) {
michael@0 504 higher = static_cast<int32_t>(AudioChannel::Publicnotification);
michael@0 505 }
michael@0 506
michael@0 507 else if (!mChannelCounters[AUDIO_CHANNEL_INT_RINGER].IsEmpty()) {
michael@0 508 higher = static_cast<int32_t>(AudioChannel::Ringer);
michael@0 509 }
michael@0 510
michael@0 511 else if (!mChannelCounters[AUDIO_CHANNEL_INT_TELEPHONY].IsEmpty()) {
michael@0 512 higher = static_cast<int32_t>(AudioChannel::Telephony);
michael@0 513 }
michael@0 514
michael@0 515 else if (!mChannelCounters[AUDIO_CHANNEL_INT_ALARM].IsEmpty()) {
michael@0 516 higher = static_cast<int32_t>(AudioChannel::Alarm);
michael@0 517 }
michael@0 518
michael@0 519 else if (!mChannelCounters[AUDIO_CHANNEL_INT_NOTIFICATION].IsEmpty()) {
michael@0 520 higher = static_cast<int32_t>(AudioChannel::Notification);
michael@0 521 }
michael@0 522
michael@0 523 else if (!mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty()) {
michael@0 524 higher = static_cast<int32_t>(AudioChannel::Content);
michael@0 525 }
michael@0 526
michael@0 527 else if (!mChannelCounters[AUDIO_CHANNEL_INT_NORMAL].IsEmpty()) {
michael@0 528 higher = static_cast<int32_t>(AudioChannel::Normal);
michael@0 529 }
michael@0 530
michael@0 531 int32_t visibleHigher = higher;
michael@0 532
michael@0 533 // Top-Down in the hierarchy for non-visible elements
michael@0 534 // And we can ignore normal channel because it can't play in the background.
michael@0 535 int32_t index;
michael@0 536 for (index = 0; kMozAudioChannelAttributeTable[index].tag; ++index);
michael@0 537
michael@0 538 for (--index;
michael@0 539 kMozAudioChannelAttributeTable[index].value > higher &&
michael@0 540 kMozAudioChannelAttributeTable[index].value > (int16_t)AudioChannel::Normal;
michael@0 541 --index) {
michael@0 542 if (kMozAudioChannelAttributeTable[index].value == (int16_t)AudioChannel::Content &&
michael@0 543 mPlayableHiddenContentChildID != CONTENT_PROCESS_ID_UNKNOWN) {
michael@0 544 higher = kMozAudioChannelAttributeTable[index].value;
michael@0 545 }
michael@0 546
michael@0 547 // Each channel type will be split to fg and bg for recording the state,
michael@0 548 // so here need to do a translation.
michael@0 549 if (!mChannelCounters[index * 2 + 1].IsEmpty()) {
michael@0 550 higher = kMozAudioChannelAttributeTable[index].value;
michael@0 551 break;
michael@0 552 }
michael@0 553 }
michael@0 554
michael@0 555 if (higher != mCurrentHigherChannel) {
michael@0 556 mCurrentHigherChannel = higher;
michael@0 557
michael@0 558 nsString channelName;
michael@0 559 if (mCurrentHigherChannel != -1) {
michael@0 560 GetAudioChannelString(static_cast<AudioChannel>(mCurrentHigherChannel),
michael@0 561 channelName);
michael@0 562 } else {
michael@0 563 channelName.AssignLiteral("none");
michael@0 564 }
michael@0 565
michael@0 566 if (obs) {
michael@0 567 obs->NotifyObservers(nullptr, "audio-channel-changed", channelName.get());
michael@0 568 }
michael@0 569 }
michael@0 570
michael@0 571 if (visibleHigher != mCurrentVisibleHigherChannel) {
michael@0 572 mCurrentVisibleHigherChannel = visibleHigher;
michael@0 573
michael@0 574 nsString channelName;
michael@0 575 if (mCurrentVisibleHigherChannel != -1) {
michael@0 576 GetAudioChannelString(static_cast<AudioChannel>(mCurrentVisibleHigherChannel),
michael@0 577 channelName);
michael@0 578 } else {
michael@0 579 channelName.AssignLiteral("none");
michael@0 580 }
michael@0 581
michael@0 582 if (obs) {
michael@0 583 obs->NotifyObservers(nullptr, "visible-audio-channel-changed", channelName.get());
michael@0 584 }
michael@0 585 }
michael@0 586 }
michael@0 587
michael@0 588 PLDHashOperator
michael@0 589 AudioChannelService::NotifyEnumerator(AudioChannelAgent* aAgent,
michael@0 590 AudioChannelAgentData* aData, void* aUnused)
michael@0 591 {
michael@0 592 MOZ_ASSERT(aAgent);
michael@0 593 aAgent->NotifyAudioChannelStateChanged();
michael@0 594 return PL_DHASH_NEXT;
michael@0 595 }
michael@0 596
michael@0 597 void
michael@0 598 AudioChannelService::Notify()
michael@0 599 {
michael@0 600 MOZ_ASSERT(NS_IsMainThread());
michael@0 601
michael@0 602 // Notify any agent for the main process.
michael@0 603 mAgents.EnumerateRead(NotifyEnumerator, nullptr);
michael@0 604
michael@0 605 // Notify for the child processes.
michael@0 606 nsTArray<ContentParent*> children;
michael@0 607 ContentParent::GetAll(children);
michael@0 608 for (uint32_t i = 0; i < children.Length(); i++) {
michael@0 609 unused << children[i]->SendAudioChannelNotify();
michael@0 610 }
michael@0 611 }
michael@0 612
michael@0 613 NS_IMETHODIMP
michael@0 614 AudioChannelService::Notify(nsITimer* aTimer)
michael@0 615 {
michael@0 616 UnregisterTypeInternal(AudioChannel::Telephony, mTimerElementHidden,
michael@0 617 mTimerChildID, false);
michael@0 618 mDeferTelChannelTimer = nullptr;
michael@0 619 return NS_OK;
michael@0 620 }
michael@0 621
michael@0 622 bool
michael@0 623 AudioChannelService::AnyAudioChannelIsActive()
michael@0 624 {
michael@0 625 for (int i = AUDIO_CHANNEL_INT_LAST - 1;
michael@0 626 i >= AUDIO_CHANNEL_INT_NORMAL; --i) {
michael@0 627 if (!mChannelCounters[i].IsEmpty()) {
michael@0 628 return true;
michael@0 629 }
michael@0 630 }
michael@0 631
michael@0 632 return false;
michael@0 633 }
michael@0 634
michael@0 635 bool
michael@0 636 AudioChannelService::ChannelsActiveWithHigherPriorityThan(
michael@0 637 AudioChannelInternalType aType)
michael@0 638 {
michael@0 639 for (int i = AUDIO_CHANNEL_INT_LAST - 1;
michael@0 640 i != AUDIO_CHANNEL_INT_CONTENT_HIDDEN; --i) {
michael@0 641 if (i == aType) {
michael@0 642 return false;
michael@0 643 }
michael@0 644
michael@0 645 if (!mChannelCounters[i].IsEmpty()) {
michael@0 646 return true;
michael@0 647 }
michael@0 648 }
michael@0 649
michael@0 650 return false;
michael@0 651 }
michael@0 652
michael@0 653 NS_IMETHODIMP
michael@0 654 AudioChannelService::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData)
michael@0 655 {
michael@0 656 if (!strcmp(aTopic, "xpcom-shutdown")) {
michael@0 657 mDisabled = true;
michael@0 658 }
michael@0 659
michael@0 660 if (!strcmp(aTopic, "ipc:content-shutdown")) {
michael@0 661 nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aSubject);
michael@0 662 if (!props) {
michael@0 663 NS_WARNING("ipc:content-shutdown message without property bag as subject");
michael@0 664 return NS_OK;
michael@0 665 }
michael@0 666
michael@0 667 int32_t index;
michael@0 668 uint64_t childID = 0;
michael@0 669 nsresult rv = props->GetPropertyAsUint64(NS_LITERAL_STRING("childID"),
michael@0 670 &childID);
michael@0 671 if (NS_SUCCEEDED(rv)) {
michael@0 672 for (int32_t type = AUDIO_CHANNEL_INT_NORMAL;
michael@0 673 type < AUDIO_CHANNEL_INT_LAST;
michael@0 674 ++type) {
michael@0 675
michael@0 676 while ((index = mChannelCounters[type].IndexOf(childID)) != -1) {
michael@0 677 mChannelCounters[type].RemoveElementAt(index);
michael@0 678 }
michael@0 679 }
michael@0 680
michael@0 681 // No hidden content channel is playable if the original playable hidden
michael@0 682 // process shuts down.
michael@0 683 if (mPlayableHiddenContentChildID == childID) {
michael@0 684 mPlayableHiddenContentChildID = CONTENT_PROCESS_ID_UNKNOWN;
michael@0 685 }
michael@0 686
michael@0 687 while ((index = mWithVideoChildIDs.IndexOf(childID)) != -1) {
michael@0 688 mWithVideoChildIDs.RemoveElementAt(index);
michael@0 689 }
michael@0 690
michael@0 691 // We don't have to remove the agents from the mAgents hashtable because if
michael@0 692 // that table contains only agents running on the same process.
michael@0 693
michael@0 694 SendAudioChannelChangedNotification(childID);
michael@0 695 Notify();
michael@0 696
michael@0 697 if (mDefChannelChildID == childID) {
michael@0 698 SetDefaultVolumeControlChannelInternal(-1, false, childID);
michael@0 699 mDefChannelChildID = CONTENT_PROCESS_ID_UNKNOWN;
michael@0 700 }
michael@0 701 } else {
michael@0 702 NS_WARNING("ipc:content-shutdown message without childID property");
michael@0 703 }
michael@0 704 }
michael@0 705 #ifdef MOZ_WIDGET_GONK
michael@0 706 // To process the volume control on each audio channel according to
michael@0 707 // change of settings
michael@0 708 else if (!strcmp(aTopic, "mozsettings-changed")) {
michael@0 709 AutoSafeJSContext cx;
michael@0 710 nsDependentString dataStr(aData);
michael@0 711 JS::Rooted<JS::Value> val(cx);
michael@0 712 if (!JS_ParseJSON(cx, dataStr.get(), dataStr.Length(), &val) ||
michael@0 713 !val.isObject()) {
michael@0 714 return NS_OK;
michael@0 715 }
michael@0 716
michael@0 717 JS::Rooted<JSObject*> obj(cx, &val.toObject());
michael@0 718 JS::Rooted<JS::Value> key(cx);
michael@0 719 if (!JS_GetProperty(cx, obj, "key", &key) ||
michael@0 720 !key.isString()) {
michael@0 721 return NS_OK;
michael@0 722 }
michael@0 723
michael@0 724 JS::Rooted<JSString*> jsKey(cx, JS::ToString(cx, key));
michael@0 725 if (!jsKey) {
michael@0 726 return NS_OK;
michael@0 727 }
michael@0 728 nsDependentJSString keyStr;
michael@0 729 if (!keyStr.init(cx, jsKey) || keyStr.Find("audio.volume.", 0, false)) {
michael@0 730 return NS_OK;
michael@0 731 }
michael@0 732
michael@0 733 JS::Rooted<JS::Value> value(cx);
michael@0 734 if (!JS_GetProperty(cx, obj, "value", &value) || !value.isInt32()) {
michael@0 735 return NS_OK;
michael@0 736 }
michael@0 737
michael@0 738 nsCOMPtr<nsIAudioManager> audioManager = do_GetService(NS_AUDIOMANAGER_CONTRACTID);
michael@0 739 NS_ENSURE_TRUE(audioManager, NS_OK);
michael@0 740
michael@0 741 int32_t index = value.toInt32();
michael@0 742 if (keyStr.EqualsLiteral("audio.volume.content")) {
michael@0 743 audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Content, index);
michael@0 744 } else if (keyStr.EqualsLiteral("audio.volume.notification")) {
michael@0 745 audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Notification, index);
michael@0 746 } else if (keyStr.EqualsLiteral("audio.volume.alarm")) {
michael@0 747 audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Alarm, index);
michael@0 748 } else if (keyStr.EqualsLiteral("audio.volume.telephony")) {
michael@0 749 audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Telephony, index);
michael@0 750 } else if (!keyStr.EqualsLiteral("audio.volume.bt_sco")) {
michael@0 751 // bt_sco is not a valid audio channel so we manipulate it in
michael@0 752 // AudioManager.cpp. And the others should not be used.
michael@0 753 // We didn't use MOZ_ASSUME_UNREACHABLE here because any web content who
michael@0 754 // has permission of mozSettings can set any names then it can be easy to
michael@0 755 // crash the B2G.
michael@0 756 NS_WARNING("unexpected audio channel for volume control");
michael@0 757 }
michael@0 758 }
michael@0 759 #endif
michael@0 760
michael@0 761 return NS_OK;
michael@0 762 }
michael@0 763
michael@0 764 AudioChannelService::AudioChannelInternalType
michael@0 765 AudioChannelService::GetInternalType(AudioChannel aChannel,
michael@0 766 bool aElementHidden)
michael@0 767 {
michael@0 768 switch (aChannel) {
michael@0 769 case AudioChannel::Normal:
michael@0 770 return aElementHidden
michael@0 771 ? AUDIO_CHANNEL_INT_NORMAL_HIDDEN
michael@0 772 : AUDIO_CHANNEL_INT_NORMAL;
michael@0 773
michael@0 774 case AudioChannel::Content:
michael@0 775 return aElementHidden
michael@0 776 ? AUDIO_CHANNEL_INT_CONTENT_HIDDEN
michael@0 777 : AUDIO_CHANNEL_INT_CONTENT;
michael@0 778
michael@0 779 case AudioChannel::Notification:
michael@0 780 return aElementHidden
michael@0 781 ? AUDIO_CHANNEL_INT_NOTIFICATION_HIDDEN
michael@0 782 : AUDIO_CHANNEL_INT_NOTIFICATION;
michael@0 783
michael@0 784 case AudioChannel::Alarm:
michael@0 785 return aElementHidden
michael@0 786 ? AUDIO_CHANNEL_INT_ALARM_HIDDEN
michael@0 787 : AUDIO_CHANNEL_INT_ALARM;
michael@0 788
michael@0 789 case AudioChannel::Telephony:
michael@0 790 return aElementHidden
michael@0 791 ? AUDIO_CHANNEL_INT_TELEPHONY_HIDDEN
michael@0 792 : AUDIO_CHANNEL_INT_TELEPHONY;
michael@0 793
michael@0 794 case AudioChannel::Ringer:
michael@0 795 return aElementHidden
michael@0 796 ? AUDIO_CHANNEL_INT_RINGER_HIDDEN
michael@0 797 : AUDIO_CHANNEL_INT_RINGER;
michael@0 798
michael@0 799 case AudioChannel::Publicnotification:
michael@0 800 return aElementHidden
michael@0 801 ? AUDIO_CHANNEL_INT_PUBLICNOTIFICATION_HIDDEN
michael@0 802 : AUDIO_CHANNEL_INT_PUBLICNOTIFICATION;
michael@0 803
michael@0 804 default:
michael@0 805 break;
michael@0 806 }
michael@0 807
michael@0 808 MOZ_CRASH("unexpected audio channel");
michael@0 809 }
michael@0 810
michael@0 811 struct RefreshAgentsVolumeData
michael@0 812 {
michael@0 813 RefreshAgentsVolumeData(nsPIDOMWindow* aWindow)
michael@0 814 : mWindow(aWindow)
michael@0 815 {}
michael@0 816
michael@0 817 nsPIDOMWindow* mWindow;
michael@0 818 nsTArray<nsRefPtr<AudioChannelAgent>> mAgents;
michael@0 819 };
michael@0 820
michael@0 821 PLDHashOperator
michael@0 822 AudioChannelService::RefreshAgentsVolumeEnumerator(AudioChannelAgent* aAgent,
michael@0 823 AudioChannelAgentData* aUnused,
michael@0 824 void* aPtr)
michael@0 825 {
michael@0 826 MOZ_ASSERT(aAgent);
michael@0 827 RefreshAgentsVolumeData* data = static_cast<RefreshAgentsVolumeData*>(aPtr);
michael@0 828 MOZ_ASSERT(data);
michael@0 829
michael@0 830 nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aAgent->Window());
michael@0 831 if (window && !window->IsInnerWindow()) {
michael@0 832 window = window->GetCurrentInnerWindow();
michael@0 833 }
michael@0 834
michael@0 835 if (window == data->mWindow) {
michael@0 836 data->mAgents.AppendElement(aAgent);
michael@0 837 }
michael@0 838
michael@0 839 return PL_DHASH_NEXT;
michael@0 840 }
michael@0 841 void
michael@0 842 AudioChannelService::RefreshAgentsVolume(nsPIDOMWindow* aWindow)
michael@0 843 {
michael@0 844 RefreshAgentsVolumeData data(aWindow);
michael@0 845 mAgents.EnumerateRead(RefreshAgentsVolumeEnumerator, &data);
michael@0 846
michael@0 847 for (uint32_t i = 0; i < data.mAgents.Length(); ++i) {
michael@0 848 data.mAgents[i]->WindowVolumeChanged();
michael@0 849 }
michael@0 850 }
michael@0 851
michael@0 852 struct CountWindowData
michael@0 853 {
michael@0 854 CountWindowData(nsIDOMWindow* aWindow)
michael@0 855 : mWindow(aWindow)
michael@0 856 , mCount(0)
michael@0 857 {}
michael@0 858
michael@0 859 nsIDOMWindow* mWindow;
michael@0 860 uint32_t mCount;
michael@0 861 };
michael@0 862
michael@0 863 PLDHashOperator
michael@0 864 AudioChannelService::CountWindowEnumerator(AudioChannelAgent* aAgent,
michael@0 865 AudioChannelAgentData* aUnused,
michael@0 866 void* aPtr)
michael@0 867 {
michael@0 868 CountWindowData* data = static_cast<CountWindowData*>(aPtr);
michael@0 869 MOZ_ASSERT(aAgent);
michael@0 870
michael@0 871 if (aAgent->Window() == data->mWindow) {
michael@0 872 ++data->mCount;
michael@0 873 }
michael@0 874
michael@0 875 return PL_DHASH_NEXT;
michael@0 876 }
michael@0 877
michael@0 878 uint32_t
michael@0 879 AudioChannelService::CountWindow(nsIDOMWindow* aWindow)
michael@0 880 {
michael@0 881 CountWindowData data(aWindow);
michael@0 882 mAgents.EnumerateRead(CountWindowEnumerator, &data);
michael@0 883 return data.mCount;
michael@0 884 }
michael@0 885
michael@0 886 /* static */ const nsAttrValue::EnumTable*
michael@0 887 AudioChannelService::GetAudioChannelTable()
michael@0 888 {
michael@0 889 return kMozAudioChannelAttributeTable;
michael@0 890 }
michael@0 891
michael@0 892 /* static */ AudioChannel
michael@0 893 AudioChannelService::GetAudioChannel(const nsAString& aChannel)
michael@0 894 {
michael@0 895 for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) {
michael@0 896 if (aChannel.EqualsASCII(kMozAudioChannelAttributeTable[i].tag)) {
michael@0 897 return static_cast<AudioChannel>(kMozAudioChannelAttributeTable[i].value);
michael@0 898 }
michael@0 899 }
michael@0 900
michael@0 901 return AudioChannel::Normal;
michael@0 902 }
michael@0 903
michael@0 904 /* static */ AudioChannel
michael@0 905 AudioChannelService::GetDefaultAudioChannel()
michael@0 906 {
michael@0 907 nsString audioChannel = Preferences::GetString("media.defaultAudioChannel");
michael@0 908 if (audioChannel.IsEmpty()) {
michael@0 909 return AudioChannel::Normal;
michael@0 910 }
michael@0 911
michael@0 912 for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) {
michael@0 913 if (audioChannel.EqualsASCII(kMozAudioChannelAttributeTable[i].tag)) {
michael@0 914 return static_cast<AudioChannel>(kMozAudioChannelAttributeTable[i].value);
michael@0 915 }
michael@0 916 }
michael@0 917
michael@0 918 return AudioChannel::Normal;
michael@0 919 }
michael@0 920
michael@0 921 /* static */ void
michael@0 922 AudioChannelService::GetAudioChannelString(AudioChannel aChannel,
michael@0 923 nsAString& aString)
michael@0 924 {
michael@0 925 aString.AssignASCII("normal");
michael@0 926
michael@0 927 for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) {
michael@0 928 if (aChannel ==
michael@0 929 static_cast<AudioChannel>(kMozAudioChannelAttributeTable[i].value)) {
michael@0 930 aString.AssignASCII(kMozAudioChannelAttributeTable[i].tag);
michael@0 931 break;
michael@0 932 }
michael@0 933 }
michael@0 934 }
michael@0 935
michael@0 936 /* static */ void
michael@0 937 AudioChannelService::GetDefaultAudioChannelString(nsAString& aString)
michael@0 938 {
michael@0 939 aString.AssignASCII("normal");
michael@0 940
michael@0 941 nsString audioChannel = Preferences::GetString("media.defaultAudioChannel");
michael@0 942 if (!audioChannel.IsEmpty()) {
michael@0 943 for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) {
michael@0 944 if (audioChannel.EqualsASCII(kMozAudioChannelAttributeTable[i].tag)) {
michael@0 945 aString = audioChannel;
michael@0 946 break;
michael@0 947 }
michael@0 948 }
michael@0 949 }
michael@0 950 }

mercurial