1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/audiochannel/AudioChannelService.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,950 @@ 1.4 +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ 1.5 +/* vim: set ts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.8 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "AudioChannelService.h" 1.11 +#include "AudioChannelServiceChild.h" 1.12 + 1.13 +#include "base/basictypes.h" 1.14 + 1.15 +#include "mozilla/Services.h" 1.16 +#include "mozilla/StaticPtr.h" 1.17 +#include "mozilla/unused.h" 1.18 + 1.19 +#include "mozilla/dom/ContentParent.h" 1.20 + 1.21 +#include "nsThreadUtils.h" 1.22 +#include "nsHashPropertyBag.h" 1.23 +#include "nsComponentManagerUtils.h" 1.24 +#include "nsPIDOMWindow.h" 1.25 +#include "nsServiceManagerUtils.h" 1.26 + 1.27 +#ifdef MOZ_WIDGET_GONK 1.28 +#include "nsJSUtils.h" 1.29 +#include "nsCxPusher.h" 1.30 +#include "nsIAudioManager.h" 1.31 +#include "SpeakerManagerService.h" 1.32 +#define NS_AUDIOMANAGER_CONTRACTID "@mozilla.org/telephony/audiomanager;1" 1.33 +#endif 1.34 + 1.35 +#include "mozilla/Preferences.h" 1.36 + 1.37 +using namespace mozilla; 1.38 +using namespace mozilla::dom; 1.39 +using namespace mozilla::hal; 1.40 + 1.41 +StaticRefPtr<AudioChannelService> gAudioChannelService; 1.42 + 1.43 +// Mappings from 'mozaudiochannel' attribute strings to an enumeration. 1.44 +static const nsAttrValue::EnumTable kMozAudioChannelAttributeTable[] = { 1.45 + { "normal", (int16_t)AudioChannel::Normal }, 1.46 + { "content", (int16_t)AudioChannel::Content }, 1.47 + { "notification", (int16_t)AudioChannel::Notification }, 1.48 + { "alarm", (int16_t)AudioChannel::Alarm }, 1.49 + { "telephony", (int16_t)AudioChannel::Telephony }, 1.50 + { "ringer", (int16_t)AudioChannel::Ringer }, 1.51 + { "publicnotification", (int16_t)AudioChannel::Publicnotification }, 1.52 + { nullptr } 1.53 +}; 1.54 + 1.55 +// static 1.56 +AudioChannelService* 1.57 +AudioChannelService::GetAudioChannelService() 1.58 +{ 1.59 + MOZ_ASSERT(NS_IsMainThread()); 1.60 + 1.61 + if (XRE_GetProcessType() != GeckoProcessType_Default) { 1.62 + return AudioChannelServiceChild::GetAudioChannelService(); 1.63 + } 1.64 + 1.65 + // If we already exist, exit early 1.66 + if (gAudioChannelService) { 1.67 + return gAudioChannelService; 1.68 + } 1.69 + 1.70 + // Create new instance, register, return 1.71 + nsRefPtr<AudioChannelService> service = new AudioChannelService(); 1.72 + NS_ENSURE_TRUE(service, nullptr); 1.73 + 1.74 + gAudioChannelService = service; 1.75 + return gAudioChannelService; 1.76 +} 1.77 + 1.78 +void 1.79 +AudioChannelService::Shutdown() 1.80 +{ 1.81 + if (XRE_GetProcessType() != GeckoProcessType_Default) { 1.82 + return AudioChannelServiceChild::Shutdown(); 1.83 + } 1.84 + 1.85 + if (gAudioChannelService) { 1.86 + gAudioChannelService = nullptr; 1.87 + } 1.88 +} 1.89 + 1.90 +NS_IMPL_ISUPPORTS(AudioChannelService, nsIObserver, nsITimerCallback) 1.91 + 1.92 +AudioChannelService::AudioChannelService() 1.93 +: mCurrentHigherChannel(-1) 1.94 +, mCurrentVisibleHigherChannel(-1) 1.95 +, mPlayableHiddenContentChildID(CONTENT_PROCESS_ID_UNKNOWN) 1.96 +, mDisabled(false) 1.97 +, mDefChannelChildID(CONTENT_PROCESS_ID_UNKNOWN) 1.98 +{ 1.99 + if (XRE_GetProcessType() == GeckoProcessType_Default) { 1.100 + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 1.101 + if (obs) { 1.102 + obs->AddObserver(this, "ipc:content-shutdown", false); 1.103 + obs->AddObserver(this, "xpcom-shutdown", false); 1.104 +#ifdef MOZ_WIDGET_GONK 1.105 + // To monitor the volume settings based on audio channel. 1.106 + obs->AddObserver(this, "mozsettings-changed", false); 1.107 +#endif 1.108 + } 1.109 + } 1.110 +} 1.111 + 1.112 +AudioChannelService::~AudioChannelService() 1.113 +{ 1.114 +} 1.115 + 1.116 +void 1.117 +AudioChannelService::RegisterAudioChannelAgent(AudioChannelAgent* aAgent, 1.118 + AudioChannel aChannel, 1.119 + bool aWithVideo) 1.120 +{ 1.121 + if (mDisabled) { 1.122 + return; 1.123 + } 1.124 + 1.125 + AudioChannelAgentData* data = new AudioChannelAgentData(aChannel, 1.126 + true /* aElementHidden */, 1.127 + AUDIO_CHANNEL_STATE_MUTED /* aState */, 1.128 + aWithVideo); 1.129 + mAgents.Put(aAgent, data); 1.130 + RegisterType(aChannel, CONTENT_PROCESS_ID_MAIN, aWithVideo); 1.131 + 1.132 + // If this is the first agent for this window, we must notify the observers. 1.133 + uint32_t count = CountWindow(aAgent->Window()); 1.134 + if (count == 1) { 1.135 + nsCOMPtr<nsIObserverService> observerService = 1.136 + services::GetObserverService(); 1.137 + if (observerService) { 1.138 + observerService->NotifyObservers(ToSupports(aAgent->Window()), 1.139 + "media-playback", 1.140 + NS_LITERAL_STRING("active").get()); 1.141 + } 1.142 + } 1.143 +} 1.144 + 1.145 +void 1.146 +AudioChannelService::RegisterType(AudioChannel aChannel, uint64_t aChildID, 1.147 + bool aWithVideo) 1.148 +{ 1.149 + if (mDisabled) { 1.150 + return; 1.151 + } 1.152 + 1.153 + AudioChannelInternalType type = GetInternalType(aChannel, true); 1.154 + mChannelCounters[type].AppendElement(aChildID); 1.155 + 1.156 + if (XRE_GetProcessType() == GeckoProcessType_Default) { 1.157 + // Since there is another telephony registered, we can unregister old one 1.158 + // immediately. 1.159 + if (mDeferTelChannelTimer && aChannel == AudioChannel::Telephony) { 1.160 + mDeferTelChannelTimer->Cancel(); 1.161 + mDeferTelChannelTimer = nullptr; 1.162 + UnregisterTypeInternal(aChannel, mTimerElementHidden, mTimerChildID, 1.163 + false); 1.164 + } 1.165 + 1.166 + if (aWithVideo) { 1.167 + mWithVideoChildIDs.AppendElement(aChildID); 1.168 + } 1.169 + 1.170 + // No hidden content channel can be playable if there is a content channel 1.171 + // in foreground (bug 855208), nor if there is a normal channel with video 1.172 + // in foreground (bug 894249). 1.173 + if (type == AUDIO_CHANNEL_INT_CONTENT || 1.174 + (type == AUDIO_CHANNEL_INT_NORMAL && 1.175 + mWithVideoChildIDs.Contains(aChildID))) { 1.176 + mPlayableHiddenContentChildID = CONTENT_PROCESS_ID_UNKNOWN; 1.177 + } 1.178 + // One hidden content channel can be playable only when there is no any 1.179 + // content channel in the foreground, and no normal channel with video in 1.180 + // foreground. 1.181 + else if (type == AUDIO_CHANNEL_INT_CONTENT_HIDDEN && 1.182 + mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty()) { 1.183 + mPlayableHiddenContentChildID = aChildID; 1.184 + } 1.185 + 1.186 + // In order to avoid race conditions, it's safer to notify any existing 1.187 + // agent any time a new one is registered. 1.188 + SendAudioChannelChangedNotification(aChildID); 1.189 + Notify(); 1.190 + } 1.191 +} 1.192 + 1.193 +void 1.194 +AudioChannelService::UnregisterAudioChannelAgent(AudioChannelAgent* aAgent) 1.195 +{ 1.196 + if (mDisabled) { 1.197 + return; 1.198 + } 1.199 + 1.200 + nsAutoPtr<AudioChannelAgentData> data; 1.201 + mAgents.RemoveAndForget(aAgent, data); 1.202 + 1.203 + if (data) { 1.204 + UnregisterType(data->mChannel, data->mElementHidden, 1.205 + CONTENT_PROCESS_ID_MAIN, data->mWithVideo); 1.206 + } 1.207 +#ifdef MOZ_WIDGET_GONK 1.208 + bool active = AnyAudioChannelIsActive(); 1.209 + for (uint32_t i = 0; i < mSpeakerManager.Length(); i++) { 1.210 + mSpeakerManager[i]->SetAudioChannelActive(active); 1.211 + } 1.212 +#endif 1.213 + 1.214 + // If this is the last agent for this window, we must notify the observers. 1.215 + uint32_t count = CountWindow(aAgent->Window()); 1.216 + if (count == 0) { 1.217 + nsCOMPtr<nsIObserverService> observerService = 1.218 + services::GetObserverService(); 1.219 + if (observerService) { 1.220 + observerService->NotifyObservers(ToSupports(aAgent->Window()), 1.221 + "media-playback", 1.222 + NS_LITERAL_STRING("inactive").get()); 1.223 + } 1.224 + } 1.225 +} 1.226 + 1.227 +void 1.228 +AudioChannelService::UnregisterType(AudioChannel aChannel, 1.229 + bool aElementHidden, 1.230 + uint64_t aChildID, 1.231 + bool aWithVideo) 1.232 +{ 1.233 + if (mDisabled) { 1.234 + return; 1.235 + } 1.236 + 1.237 + // There are two reasons to defer the decrease of telephony channel. 1.238 + // 1. User can have time to remove device from his ear before music resuming. 1.239 + // 2. Give BT SCO to be disconnected before starting to connect A2DP. 1.240 + if (XRE_GetProcessType() == GeckoProcessType_Default && 1.241 + aChannel == AudioChannel::Telephony && 1.242 + (mChannelCounters[AUDIO_CHANNEL_INT_TELEPHONY_HIDDEN].Length() + 1.243 + mChannelCounters[AUDIO_CHANNEL_INT_TELEPHONY].Length()) == 1) { 1.244 + mTimerElementHidden = aElementHidden; 1.245 + mTimerChildID = aChildID; 1.246 + mDeferTelChannelTimer = do_CreateInstance("@mozilla.org/timer;1"); 1.247 + mDeferTelChannelTimer->InitWithCallback(this, 1500, nsITimer::TYPE_ONE_SHOT); 1.248 + return; 1.249 + } 1.250 + 1.251 + UnregisterTypeInternal(aChannel, aElementHidden, aChildID, aWithVideo); 1.252 +} 1.253 + 1.254 +void 1.255 +AudioChannelService::UnregisterTypeInternal(AudioChannel aChannel, 1.256 + bool aElementHidden, 1.257 + uint64_t aChildID, 1.258 + bool aWithVideo) 1.259 +{ 1.260 + // The array may contain multiple occurrence of this appId but 1.261 + // this should remove only the first one. 1.262 + AudioChannelInternalType type = GetInternalType(aChannel, aElementHidden); 1.263 + MOZ_ASSERT(mChannelCounters[type].Contains(aChildID)); 1.264 + mChannelCounters[type].RemoveElement(aChildID); 1.265 + 1.266 + // In order to avoid race conditions, it's safer to notify any existing 1.267 + // agent any time a new one is registered. 1.268 + if (XRE_GetProcessType() == GeckoProcessType_Default) { 1.269 + // No hidden content channel is playable if the original playable hidden 1.270 + // process does not need to play audio from background anymore. 1.271 + if (aChannel == AudioChannel::Content && 1.272 + mPlayableHiddenContentChildID == aChildID && 1.273 + !mChannelCounters[AUDIO_CHANNEL_INT_CONTENT_HIDDEN].Contains(aChildID)) { 1.274 + mPlayableHiddenContentChildID = CONTENT_PROCESS_ID_UNKNOWN; 1.275 + } 1.276 + 1.277 + if (aWithVideo) { 1.278 + MOZ_ASSERT(mWithVideoChildIDs.Contains(aChildID)); 1.279 + mWithVideoChildIDs.RemoveElement(aChildID); 1.280 + } 1.281 + 1.282 + SendAudioChannelChangedNotification(aChildID); 1.283 + Notify(); 1.284 + } 1.285 +} 1.286 + 1.287 +void 1.288 +AudioChannelService::UpdateChannelType(AudioChannel aChannel, 1.289 + uint64_t aChildID, 1.290 + bool aElementHidden, 1.291 + bool aElementWasHidden) 1.292 +{ 1.293 + // Calculate the new and old internal type and update the hashtable if needed. 1.294 + AudioChannelInternalType newType = GetInternalType(aChannel, aElementHidden); 1.295 + AudioChannelInternalType oldType = GetInternalType(aChannel, aElementWasHidden); 1.296 + 1.297 + if (newType != oldType) { 1.298 + mChannelCounters[newType].AppendElement(aChildID); 1.299 + MOZ_ASSERT(mChannelCounters[oldType].Contains(aChildID)); 1.300 + mChannelCounters[oldType].RemoveElement(aChildID); 1.301 + } 1.302 + 1.303 + // No hidden content channel can be playable if there is a content channel 1.304 + // in foreground (bug 855208), nor if there is a normal channel with video 1.305 + // in foreground (bug 894249). 1.306 + if (newType == AUDIO_CHANNEL_INT_CONTENT || 1.307 + (newType == AUDIO_CHANNEL_INT_NORMAL && 1.308 + mWithVideoChildIDs.Contains(aChildID))) { 1.309 + mPlayableHiddenContentChildID = CONTENT_PROCESS_ID_UNKNOWN; 1.310 + } 1.311 + // If there is no content channel in foreground and no normal channel with 1.312 + // video in foreground, the last content channel which goes from foreground 1.313 + // to background can be playable. 1.314 + else if (oldType == AUDIO_CHANNEL_INT_CONTENT && 1.315 + newType == AUDIO_CHANNEL_INT_CONTENT_HIDDEN && 1.316 + mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty()) { 1.317 + mPlayableHiddenContentChildID = aChildID; 1.318 + } 1.319 +} 1.320 + 1.321 +AudioChannelState 1.322 +AudioChannelService::GetState(AudioChannelAgent* aAgent, bool aElementHidden) 1.323 +{ 1.324 + AudioChannelAgentData* data; 1.325 + if (!mAgents.Get(aAgent, &data)) { 1.326 + return AUDIO_CHANNEL_STATE_MUTED; 1.327 + } 1.328 + 1.329 + bool oldElementHidden = data->mElementHidden; 1.330 + // Update visibility. 1.331 + data->mElementHidden = aElementHidden; 1.332 + 1.333 + data->mState = GetStateInternal(data->mChannel, CONTENT_PROCESS_ID_MAIN, 1.334 + aElementHidden, oldElementHidden); 1.335 + return data->mState; 1.336 +} 1.337 + 1.338 +AudioChannelState 1.339 +AudioChannelService::GetStateInternal(AudioChannel aChannel, uint64_t aChildID, 1.340 + bool aElementHidden, 1.341 + bool aElementWasHidden) 1.342 +{ 1.343 + UpdateChannelType(aChannel, aChildID, aElementHidden, aElementWasHidden); 1.344 + 1.345 + // Calculating the new and old type and update the hashtable if needed. 1.346 + AudioChannelInternalType newType = GetInternalType(aChannel, aElementHidden); 1.347 + AudioChannelInternalType oldType = GetInternalType(aChannel, 1.348 + aElementWasHidden); 1.349 + 1.350 + if (newType != oldType && 1.351 + (aChannel == AudioChannel::Content || 1.352 + (aChannel == AudioChannel::Normal && 1.353 + mWithVideoChildIDs.Contains(aChildID)))) { 1.354 + Notify(); 1.355 + } 1.356 + 1.357 + SendAudioChannelChangedNotification(aChildID); 1.358 + 1.359 + // Let play any visible audio channel. 1.360 + if (!aElementHidden) { 1.361 + if (CheckVolumeFadedCondition(newType, aElementHidden)) { 1.362 + return AUDIO_CHANNEL_STATE_FADED; 1.363 + } 1.364 + return AUDIO_CHANNEL_STATE_NORMAL; 1.365 + } 1.366 + 1.367 + // We are not visible, maybe we have to mute. 1.368 + if (newType == AUDIO_CHANNEL_INT_NORMAL_HIDDEN || 1.369 + (newType == AUDIO_CHANNEL_INT_CONTENT_HIDDEN && 1.370 + // One process can have multiple content channels; and during the 1.371 + // transition from foreground to background, its content channels will be 1.372 + // updated with correct visibility status one by one. All its content 1.373 + // channels should remain playable until all of their visibility statuses 1.374 + // have been updated as hidden. After all its content channels have been 1.375 + // updated properly as hidden, mPlayableHiddenContentChildID is used to 1.376 + // check whether this background process is playable or not. 1.377 + !(mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].Contains(aChildID) || 1.378 + (mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty() && 1.379 + mPlayableHiddenContentChildID == aChildID)))) { 1.380 + return AUDIO_CHANNEL_STATE_MUTED; 1.381 + } 1.382 + 1.383 + // After checking the condition on normal & content channel, if the state 1.384 + // is not on muted then checking other higher channels type here. 1.385 + if (ChannelsActiveWithHigherPriorityThan(newType)) { 1.386 + MOZ_ASSERT(newType != AUDIO_CHANNEL_INT_NORMAL_HIDDEN); 1.387 + if (CheckVolumeFadedCondition(newType, aElementHidden)) { 1.388 + return AUDIO_CHANNEL_STATE_FADED; 1.389 + } 1.390 + return AUDIO_CHANNEL_STATE_MUTED; 1.391 + } 1.392 + 1.393 + return AUDIO_CHANNEL_STATE_NORMAL; 1.394 +} 1.395 + 1.396 +bool 1.397 +AudioChannelService::CheckVolumeFadedCondition(AudioChannelInternalType aType, 1.398 + bool aElementHidden) 1.399 +{ 1.400 + // Only normal & content channels are considered 1.401 + if (aType > AUDIO_CHANNEL_INT_CONTENT_HIDDEN) { 1.402 + return false; 1.403 + } 1.404 + 1.405 + // Consider that audio from notification is with short duration 1.406 + // so just fade the volume not pause it 1.407 + if (mChannelCounters[AUDIO_CHANNEL_INT_NOTIFICATION].IsEmpty() && 1.408 + mChannelCounters[AUDIO_CHANNEL_INT_NOTIFICATION_HIDDEN].IsEmpty()) { 1.409 + return false; 1.410 + } 1.411 + 1.412 + // Since this element is on the foreground, it can be allowed to play always. 1.413 + // So return true directly when there is any notification channel alive. 1.414 + if (aElementHidden == false) { 1.415 + return true; 1.416 + } 1.417 + 1.418 + // If element is on the background, it is possible paused by channels higher 1.419 + // then notification. 1.420 + for (int i = AUDIO_CHANNEL_INT_LAST - 1; 1.421 + i != AUDIO_CHANNEL_INT_NOTIFICATION_HIDDEN; --i) { 1.422 + if (!mChannelCounters[i].IsEmpty()) { 1.423 + return false; 1.424 + } 1.425 + } 1.426 + 1.427 + return true; 1.428 +} 1.429 + 1.430 +bool 1.431 +AudioChannelService::ContentOrNormalChannelIsActive() 1.432 +{ 1.433 + return !mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty() || 1.434 + !mChannelCounters[AUDIO_CHANNEL_INT_CONTENT_HIDDEN].IsEmpty() || 1.435 + !mChannelCounters[AUDIO_CHANNEL_INT_NORMAL].IsEmpty(); 1.436 +} 1.437 + 1.438 +bool 1.439 +AudioChannelService::ProcessContentOrNormalChannelIsActive(uint64_t aChildID) 1.440 +{ 1.441 + return mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].Contains(aChildID) || 1.442 + mChannelCounters[AUDIO_CHANNEL_INT_CONTENT_HIDDEN].Contains(aChildID) || 1.443 + mChannelCounters[AUDIO_CHANNEL_INT_NORMAL].Contains(aChildID); 1.444 +} 1.445 + 1.446 +void 1.447 +AudioChannelService::SetDefaultVolumeControlChannel(int32_t aChannel, 1.448 + bool aHidden) 1.449 +{ 1.450 + SetDefaultVolumeControlChannelInternal(aChannel, aHidden, 1.451 + CONTENT_PROCESS_ID_MAIN); 1.452 +} 1.453 + 1.454 +void 1.455 +AudioChannelService::SetDefaultVolumeControlChannelInternal(int32_t aChannel, 1.456 + bool aHidden, 1.457 + uint64_t aChildID) 1.458 +{ 1.459 + if (XRE_GetProcessType() != GeckoProcessType_Default) { 1.460 + return; 1.461 + } 1.462 + 1.463 + // If this child is in the background and mDefChannelChildID is set to 1.464 + // others then it means other child in the foreground already set it's 1.465 + // own default channel already. 1.466 + if (!aHidden && mDefChannelChildID != aChildID) { 1.467 + return; 1.468 + } 1.469 + 1.470 + mDefChannelChildID = aChildID; 1.471 + nsString channelName; 1.472 + 1.473 + if (aChannel == -1) { 1.474 + channelName.AssignASCII("unknown"); 1.475 + } else { 1.476 + GetAudioChannelString(static_cast<AudioChannel>(aChannel), channelName); 1.477 + } 1.478 + 1.479 + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 1.480 + if (obs) { 1.481 + obs->NotifyObservers(nullptr, "default-volume-channel-changed", 1.482 + channelName.get()); 1.483 + } 1.484 +} 1.485 + 1.486 +void 1.487 +AudioChannelService::SendAudioChannelChangedNotification(uint64_t aChildID) 1.488 +{ 1.489 + if (XRE_GetProcessType() != GeckoProcessType_Default) { 1.490 + return; 1.491 + } 1.492 + 1.493 + nsRefPtr<nsHashPropertyBag> props = new nsHashPropertyBag(); 1.494 + props->SetPropertyAsUint64(NS_LITERAL_STRING("childID"), aChildID); 1.495 + 1.496 + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 1.497 + if (obs) { 1.498 + obs->NotifyObservers(static_cast<nsIWritablePropertyBag*>(props), 1.499 + "audio-channel-process-changed", nullptr); 1.500 + } 1.501 + 1.502 + // Calculating the most important active channel. 1.503 + int32_t higher = -1; 1.504 + 1.505 + // Top-Down in the hierarchy for visible elements 1.506 + if (!mChannelCounters[AUDIO_CHANNEL_INT_PUBLICNOTIFICATION].IsEmpty()) { 1.507 + higher = static_cast<int32_t>(AudioChannel::Publicnotification); 1.508 + } 1.509 + 1.510 + else if (!mChannelCounters[AUDIO_CHANNEL_INT_RINGER].IsEmpty()) { 1.511 + higher = static_cast<int32_t>(AudioChannel::Ringer); 1.512 + } 1.513 + 1.514 + else if (!mChannelCounters[AUDIO_CHANNEL_INT_TELEPHONY].IsEmpty()) { 1.515 + higher = static_cast<int32_t>(AudioChannel::Telephony); 1.516 + } 1.517 + 1.518 + else if (!mChannelCounters[AUDIO_CHANNEL_INT_ALARM].IsEmpty()) { 1.519 + higher = static_cast<int32_t>(AudioChannel::Alarm); 1.520 + } 1.521 + 1.522 + else if (!mChannelCounters[AUDIO_CHANNEL_INT_NOTIFICATION].IsEmpty()) { 1.523 + higher = static_cast<int32_t>(AudioChannel::Notification); 1.524 + } 1.525 + 1.526 + else if (!mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty()) { 1.527 + higher = static_cast<int32_t>(AudioChannel::Content); 1.528 + } 1.529 + 1.530 + else if (!mChannelCounters[AUDIO_CHANNEL_INT_NORMAL].IsEmpty()) { 1.531 + higher = static_cast<int32_t>(AudioChannel::Normal); 1.532 + } 1.533 + 1.534 + int32_t visibleHigher = higher; 1.535 + 1.536 + // Top-Down in the hierarchy for non-visible elements 1.537 + // And we can ignore normal channel because it can't play in the background. 1.538 + int32_t index; 1.539 + for (index = 0; kMozAudioChannelAttributeTable[index].tag; ++index); 1.540 + 1.541 + for (--index; 1.542 + kMozAudioChannelAttributeTable[index].value > higher && 1.543 + kMozAudioChannelAttributeTable[index].value > (int16_t)AudioChannel::Normal; 1.544 + --index) { 1.545 + if (kMozAudioChannelAttributeTable[index].value == (int16_t)AudioChannel::Content && 1.546 + mPlayableHiddenContentChildID != CONTENT_PROCESS_ID_UNKNOWN) { 1.547 + higher = kMozAudioChannelAttributeTable[index].value; 1.548 + } 1.549 + 1.550 + // Each channel type will be split to fg and bg for recording the state, 1.551 + // so here need to do a translation. 1.552 + if (!mChannelCounters[index * 2 + 1].IsEmpty()) { 1.553 + higher = kMozAudioChannelAttributeTable[index].value; 1.554 + break; 1.555 + } 1.556 + } 1.557 + 1.558 + if (higher != mCurrentHigherChannel) { 1.559 + mCurrentHigherChannel = higher; 1.560 + 1.561 + nsString channelName; 1.562 + if (mCurrentHigherChannel != -1) { 1.563 + GetAudioChannelString(static_cast<AudioChannel>(mCurrentHigherChannel), 1.564 + channelName); 1.565 + } else { 1.566 + channelName.AssignLiteral("none"); 1.567 + } 1.568 + 1.569 + if (obs) { 1.570 + obs->NotifyObservers(nullptr, "audio-channel-changed", channelName.get()); 1.571 + } 1.572 + } 1.573 + 1.574 + if (visibleHigher != mCurrentVisibleHigherChannel) { 1.575 + mCurrentVisibleHigherChannel = visibleHigher; 1.576 + 1.577 + nsString channelName; 1.578 + if (mCurrentVisibleHigherChannel != -1) { 1.579 + GetAudioChannelString(static_cast<AudioChannel>(mCurrentVisibleHigherChannel), 1.580 + channelName); 1.581 + } else { 1.582 + channelName.AssignLiteral("none"); 1.583 + } 1.584 + 1.585 + if (obs) { 1.586 + obs->NotifyObservers(nullptr, "visible-audio-channel-changed", channelName.get()); 1.587 + } 1.588 + } 1.589 +} 1.590 + 1.591 +PLDHashOperator 1.592 +AudioChannelService::NotifyEnumerator(AudioChannelAgent* aAgent, 1.593 + AudioChannelAgentData* aData, void* aUnused) 1.594 +{ 1.595 + MOZ_ASSERT(aAgent); 1.596 + aAgent->NotifyAudioChannelStateChanged(); 1.597 + return PL_DHASH_NEXT; 1.598 +} 1.599 + 1.600 +void 1.601 +AudioChannelService::Notify() 1.602 +{ 1.603 + MOZ_ASSERT(NS_IsMainThread()); 1.604 + 1.605 + // Notify any agent for the main process. 1.606 + mAgents.EnumerateRead(NotifyEnumerator, nullptr); 1.607 + 1.608 + // Notify for the child processes. 1.609 + nsTArray<ContentParent*> children; 1.610 + ContentParent::GetAll(children); 1.611 + for (uint32_t i = 0; i < children.Length(); i++) { 1.612 + unused << children[i]->SendAudioChannelNotify(); 1.613 + } 1.614 +} 1.615 + 1.616 +NS_IMETHODIMP 1.617 +AudioChannelService::Notify(nsITimer* aTimer) 1.618 +{ 1.619 + UnregisterTypeInternal(AudioChannel::Telephony, mTimerElementHidden, 1.620 + mTimerChildID, false); 1.621 + mDeferTelChannelTimer = nullptr; 1.622 + return NS_OK; 1.623 +} 1.624 + 1.625 +bool 1.626 +AudioChannelService::AnyAudioChannelIsActive() 1.627 +{ 1.628 + for (int i = AUDIO_CHANNEL_INT_LAST - 1; 1.629 + i >= AUDIO_CHANNEL_INT_NORMAL; --i) { 1.630 + if (!mChannelCounters[i].IsEmpty()) { 1.631 + return true; 1.632 + } 1.633 + } 1.634 + 1.635 + return false; 1.636 +} 1.637 + 1.638 +bool 1.639 +AudioChannelService::ChannelsActiveWithHigherPriorityThan( 1.640 + AudioChannelInternalType aType) 1.641 +{ 1.642 + for (int i = AUDIO_CHANNEL_INT_LAST - 1; 1.643 + i != AUDIO_CHANNEL_INT_CONTENT_HIDDEN; --i) { 1.644 + if (i == aType) { 1.645 + return false; 1.646 + } 1.647 + 1.648 + if (!mChannelCounters[i].IsEmpty()) { 1.649 + return true; 1.650 + } 1.651 + } 1.652 + 1.653 + return false; 1.654 +} 1.655 + 1.656 +NS_IMETHODIMP 1.657 +AudioChannelService::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) 1.658 +{ 1.659 + if (!strcmp(aTopic, "xpcom-shutdown")) { 1.660 + mDisabled = true; 1.661 + } 1.662 + 1.663 + if (!strcmp(aTopic, "ipc:content-shutdown")) { 1.664 + nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aSubject); 1.665 + if (!props) { 1.666 + NS_WARNING("ipc:content-shutdown message without property bag as subject"); 1.667 + return NS_OK; 1.668 + } 1.669 + 1.670 + int32_t index; 1.671 + uint64_t childID = 0; 1.672 + nsresult rv = props->GetPropertyAsUint64(NS_LITERAL_STRING("childID"), 1.673 + &childID); 1.674 + if (NS_SUCCEEDED(rv)) { 1.675 + for (int32_t type = AUDIO_CHANNEL_INT_NORMAL; 1.676 + type < AUDIO_CHANNEL_INT_LAST; 1.677 + ++type) { 1.678 + 1.679 + while ((index = mChannelCounters[type].IndexOf(childID)) != -1) { 1.680 + mChannelCounters[type].RemoveElementAt(index); 1.681 + } 1.682 + } 1.683 + 1.684 + // No hidden content channel is playable if the original playable hidden 1.685 + // process shuts down. 1.686 + if (mPlayableHiddenContentChildID == childID) { 1.687 + mPlayableHiddenContentChildID = CONTENT_PROCESS_ID_UNKNOWN; 1.688 + } 1.689 + 1.690 + while ((index = mWithVideoChildIDs.IndexOf(childID)) != -1) { 1.691 + mWithVideoChildIDs.RemoveElementAt(index); 1.692 + } 1.693 + 1.694 + // We don't have to remove the agents from the mAgents hashtable because if 1.695 + // that table contains only agents running on the same process. 1.696 + 1.697 + SendAudioChannelChangedNotification(childID); 1.698 + Notify(); 1.699 + 1.700 + if (mDefChannelChildID == childID) { 1.701 + SetDefaultVolumeControlChannelInternal(-1, false, childID); 1.702 + mDefChannelChildID = CONTENT_PROCESS_ID_UNKNOWN; 1.703 + } 1.704 + } else { 1.705 + NS_WARNING("ipc:content-shutdown message without childID property"); 1.706 + } 1.707 + } 1.708 +#ifdef MOZ_WIDGET_GONK 1.709 + // To process the volume control on each audio channel according to 1.710 + // change of settings 1.711 + else if (!strcmp(aTopic, "mozsettings-changed")) { 1.712 + AutoSafeJSContext cx; 1.713 + nsDependentString dataStr(aData); 1.714 + JS::Rooted<JS::Value> val(cx); 1.715 + if (!JS_ParseJSON(cx, dataStr.get(), dataStr.Length(), &val) || 1.716 + !val.isObject()) { 1.717 + return NS_OK; 1.718 + } 1.719 + 1.720 + JS::Rooted<JSObject*> obj(cx, &val.toObject()); 1.721 + JS::Rooted<JS::Value> key(cx); 1.722 + if (!JS_GetProperty(cx, obj, "key", &key) || 1.723 + !key.isString()) { 1.724 + return NS_OK; 1.725 + } 1.726 + 1.727 + JS::Rooted<JSString*> jsKey(cx, JS::ToString(cx, key)); 1.728 + if (!jsKey) { 1.729 + return NS_OK; 1.730 + } 1.731 + nsDependentJSString keyStr; 1.732 + if (!keyStr.init(cx, jsKey) || keyStr.Find("audio.volume.", 0, false)) { 1.733 + return NS_OK; 1.734 + } 1.735 + 1.736 + JS::Rooted<JS::Value> value(cx); 1.737 + if (!JS_GetProperty(cx, obj, "value", &value) || !value.isInt32()) { 1.738 + return NS_OK; 1.739 + } 1.740 + 1.741 + nsCOMPtr<nsIAudioManager> audioManager = do_GetService(NS_AUDIOMANAGER_CONTRACTID); 1.742 + NS_ENSURE_TRUE(audioManager, NS_OK); 1.743 + 1.744 + int32_t index = value.toInt32(); 1.745 + if (keyStr.EqualsLiteral("audio.volume.content")) { 1.746 + audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Content, index); 1.747 + } else if (keyStr.EqualsLiteral("audio.volume.notification")) { 1.748 + audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Notification, index); 1.749 + } else if (keyStr.EqualsLiteral("audio.volume.alarm")) { 1.750 + audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Alarm, index); 1.751 + } else if (keyStr.EqualsLiteral("audio.volume.telephony")) { 1.752 + audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Telephony, index); 1.753 + } else if (!keyStr.EqualsLiteral("audio.volume.bt_sco")) { 1.754 + // bt_sco is not a valid audio channel so we manipulate it in 1.755 + // AudioManager.cpp. And the others should not be used. 1.756 + // We didn't use MOZ_ASSUME_UNREACHABLE here because any web content who 1.757 + // has permission of mozSettings can set any names then it can be easy to 1.758 + // crash the B2G. 1.759 + NS_WARNING("unexpected audio channel for volume control"); 1.760 + } 1.761 + } 1.762 +#endif 1.763 + 1.764 + return NS_OK; 1.765 +} 1.766 + 1.767 +AudioChannelService::AudioChannelInternalType 1.768 +AudioChannelService::GetInternalType(AudioChannel aChannel, 1.769 + bool aElementHidden) 1.770 +{ 1.771 + switch (aChannel) { 1.772 + case AudioChannel::Normal: 1.773 + return aElementHidden 1.774 + ? AUDIO_CHANNEL_INT_NORMAL_HIDDEN 1.775 + : AUDIO_CHANNEL_INT_NORMAL; 1.776 + 1.777 + case AudioChannel::Content: 1.778 + return aElementHidden 1.779 + ? AUDIO_CHANNEL_INT_CONTENT_HIDDEN 1.780 + : AUDIO_CHANNEL_INT_CONTENT; 1.781 + 1.782 + case AudioChannel::Notification: 1.783 + return aElementHidden 1.784 + ? AUDIO_CHANNEL_INT_NOTIFICATION_HIDDEN 1.785 + : AUDIO_CHANNEL_INT_NOTIFICATION; 1.786 + 1.787 + case AudioChannel::Alarm: 1.788 + return aElementHidden 1.789 + ? AUDIO_CHANNEL_INT_ALARM_HIDDEN 1.790 + : AUDIO_CHANNEL_INT_ALARM; 1.791 + 1.792 + case AudioChannel::Telephony: 1.793 + return aElementHidden 1.794 + ? AUDIO_CHANNEL_INT_TELEPHONY_HIDDEN 1.795 + : AUDIO_CHANNEL_INT_TELEPHONY; 1.796 + 1.797 + case AudioChannel::Ringer: 1.798 + return aElementHidden 1.799 + ? AUDIO_CHANNEL_INT_RINGER_HIDDEN 1.800 + : AUDIO_CHANNEL_INT_RINGER; 1.801 + 1.802 + case AudioChannel::Publicnotification: 1.803 + return aElementHidden 1.804 + ? AUDIO_CHANNEL_INT_PUBLICNOTIFICATION_HIDDEN 1.805 + : AUDIO_CHANNEL_INT_PUBLICNOTIFICATION; 1.806 + 1.807 + default: 1.808 + break; 1.809 + } 1.810 + 1.811 + MOZ_CRASH("unexpected audio channel"); 1.812 +} 1.813 + 1.814 +struct RefreshAgentsVolumeData 1.815 +{ 1.816 + RefreshAgentsVolumeData(nsPIDOMWindow* aWindow) 1.817 + : mWindow(aWindow) 1.818 + {} 1.819 + 1.820 + nsPIDOMWindow* mWindow; 1.821 + nsTArray<nsRefPtr<AudioChannelAgent>> mAgents; 1.822 +}; 1.823 + 1.824 +PLDHashOperator 1.825 +AudioChannelService::RefreshAgentsVolumeEnumerator(AudioChannelAgent* aAgent, 1.826 + AudioChannelAgentData* aUnused, 1.827 + void* aPtr) 1.828 +{ 1.829 + MOZ_ASSERT(aAgent); 1.830 + RefreshAgentsVolumeData* data = static_cast<RefreshAgentsVolumeData*>(aPtr); 1.831 + MOZ_ASSERT(data); 1.832 + 1.833 + nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aAgent->Window()); 1.834 + if (window && !window->IsInnerWindow()) { 1.835 + window = window->GetCurrentInnerWindow(); 1.836 + } 1.837 + 1.838 + if (window == data->mWindow) { 1.839 + data->mAgents.AppendElement(aAgent); 1.840 + } 1.841 + 1.842 + return PL_DHASH_NEXT; 1.843 +} 1.844 +void 1.845 +AudioChannelService::RefreshAgentsVolume(nsPIDOMWindow* aWindow) 1.846 +{ 1.847 + RefreshAgentsVolumeData data(aWindow); 1.848 + mAgents.EnumerateRead(RefreshAgentsVolumeEnumerator, &data); 1.849 + 1.850 + for (uint32_t i = 0; i < data.mAgents.Length(); ++i) { 1.851 + data.mAgents[i]->WindowVolumeChanged(); 1.852 + } 1.853 +} 1.854 + 1.855 +struct CountWindowData 1.856 +{ 1.857 + CountWindowData(nsIDOMWindow* aWindow) 1.858 + : mWindow(aWindow) 1.859 + , mCount(0) 1.860 + {} 1.861 + 1.862 + nsIDOMWindow* mWindow; 1.863 + uint32_t mCount; 1.864 +}; 1.865 + 1.866 +PLDHashOperator 1.867 +AudioChannelService::CountWindowEnumerator(AudioChannelAgent* aAgent, 1.868 + AudioChannelAgentData* aUnused, 1.869 + void* aPtr) 1.870 +{ 1.871 + CountWindowData* data = static_cast<CountWindowData*>(aPtr); 1.872 + MOZ_ASSERT(aAgent); 1.873 + 1.874 + if (aAgent->Window() == data->mWindow) { 1.875 + ++data->mCount; 1.876 + } 1.877 + 1.878 + return PL_DHASH_NEXT; 1.879 +} 1.880 + 1.881 +uint32_t 1.882 +AudioChannelService::CountWindow(nsIDOMWindow* aWindow) 1.883 +{ 1.884 + CountWindowData data(aWindow); 1.885 + mAgents.EnumerateRead(CountWindowEnumerator, &data); 1.886 + return data.mCount; 1.887 +} 1.888 + 1.889 +/* static */ const nsAttrValue::EnumTable* 1.890 +AudioChannelService::GetAudioChannelTable() 1.891 +{ 1.892 + return kMozAudioChannelAttributeTable; 1.893 +} 1.894 + 1.895 +/* static */ AudioChannel 1.896 +AudioChannelService::GetAudioChannel(const nsAString& aChannel) 1.897 +{ 1.898 + for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) { 1.899 + if (aChannel.EqualsASCII(kMozAudioChannelAttributeTable[i].tag)) { 1.900 + return static_cast<AudioChannel>(kMozAudioChannelAttributeTable[i].value); 1.901 + } 1.902 + } 1.903 + 1.904 + return AudioChannel::Normal; 1.905 +} 1.906 + 1.907 +/* static */ AudioChannel 1.908 +AudioChannelService::GetDefaultAudioChannel() 1.909 +{ 1.910 + nsString audioChannel = Preferences::GetString("media.defaultAudioChannel"); 1.911 + if (audioChannel.IsEmpty()) { 1.912 + return AudioChannel::Normal; 1.913 + } 1.914 + 1.915 + for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) { 1.916 + if (audioChannel.EqualsASCII(kMozAudioChannelAttributeTable[i].tag)) { 1.917 + return static_cast<AudioChannel>(kMozAudioChannelAttributeTable[i].value); 1.918 + } 1.919 + } 1.920 + 1.921 + return AudioChannel::Normal; 1.922 +} 1.923 + 1.924 +/* static */ void 1.925 +AudioChannelService::GetAudioChannelString(AudioChannel aChannel, 1.926 + nsAString& aString) 1.927 +{ 1.928 + aString.AssignASCII("normal"); 1.929 + 1.930 + for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) { 1.931 + if (aChannel == 1.932 + static_cast<AudioChannel>(kMozAudioChannelAttributeTable[i].value)) { 1.933 + aString.AssignASCII(kMozAudioChannelAttributeTable[i].tag); 1.934 + break; 1.935 + } 1.936 + } 1.937 +} 1.938 + 1.939 +/* static */ void 1.940 +AudioChannelService::GetDefaultAudioChannelString(nsAString& aString) 1.941 +{ 1.942 + aString.AssignASCII("normal"); 1.943 + 1.944 + nsString audioChannel = Preferences::GetString("media.defaultAudioChannel"); 1.945 + if (!audioChannel.IsEmpty()) { 1.946 + for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) { 1.947 + if (audioChannel.EqualsASCII(kMozAudioChannelAttributeTable[i].tag)) { 1.948 + aString = audioChannel; 1.949 + break; 1.950 + } 1.951 + } 1.952 + } 1.953 +}