dom/audiochannel/AudioChannelService.cpp

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

mercurial