dom/audiochannel/AudioChannelService.cpp

changeset 0
6474c204b198
     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 +}

mercurial