diff --git a/dom/apps/PermissionsTable.jsm b/dom/apps/PermissionsTable.jsm index b2853756d6..614943f05a 100644 --- a/dom/apps/PermissionsTable.jsm +++ b/dom/apps/PermissionsTable.jsm @@ -348,6 +348,12 @@ this.PermissionsTable = { geolocation: { privileged: ALLOW_ACTION, certified: ALLOW_ACTION }, + "audio-channel-system": { + app: DENY_ACTION, + trusted: DENY_ACTION, + privileged: ALLOW_ACTION, + certified: ALLOW_ACTION + }, "audio-channel-telephony": { app: DENY_ACTION, trusted: DENY_ACTION, diff --git a/dom/audiochannel/AudioChannelAgent.cpp b/dom/audiochannel/AudioChannelAgent.cpp index 7ae28aea66..ca3ffabb5a 100644 --- a/dom/audiochannel/AudioChannelAgent.cpp +++ b/dom/audiochannel/AudioChannelAgent.cpp @@ -72,6 +72,7 @@ AudioChannelAgent::InitInternal(nsIDOMWindow* aWindow, int32_t aChannelType, int(AUDIO_AGENT_CHANNEL_ALARM) == int(AudioChannel::Alarm) && int(AUDIO_AGENT_CHANNEL_TELEPHONY) == int(AudioChannel::Telephony) && int(AUDIO_AGENT_CHANNEL_RINGER) == int(AudioChannel::Ringer) && + int(AUDIO_AGENT_CHANNEL_SYSTEM) == int(AudioChannel::System) && int(AUDIO_AGENT_CHANNEL_PUBLICNOTIFICATION) == int(AudioChannel::Publicnotification), "Enum of channel on nsIAudioChannelAgent.idl should be the same with AudioChannelBinding.h"); diff --git a/dom/audiochannel/AudioChannelService.cpp b/dom/audiochannel/AudioChannelService.cpp index a1534ae49b..28747f8f3e 100644 --- a/dom/audiochannel/AudioChannelService.cpp +++ b/dom/audiochannel/AudioChannelService.cpp @@ -27,9 +27,7 @@ #ifdef MOZ_WIDGET_GONK #include "nsJSUtils.h" -#include "nsIAudioManager.h" #include "SpeakerManagerService.h" -#define NS_AUDIOMANAGER_CONTRACTID "@mozilla.org/telephony/audiomanager;1" #endif #include "mozilla/Preferences.h" @@ -205,8 +203,7 @@ NS_IMPL_ADDREF(AudioChannelService) NS_IMPL_RELEASE(AudioChannelService) AudioChannelService::AudioChannelService() - : mDisabled(false) - , mDefChannelChildID(CONTENT_PROCESS_ID_UNKNOWN) + : mDefChannelChildID(CONTENT_PROCESS_ID_UNKNOWN) , mTelephonyChannel(false) , mContentOrNormalChannel(false) , mAnyChannel(false) @@ -236,10 +233,6 @@ void AudioChannelService::RegisterAudioChannelAgent(AudioChannelAgent* aAgent, AudioChannel aChannel) { - if (mDisabled) { - return; - } - uint64_t windowID = aAgent->WindowID(); AudioChannelWindow* winData = GetWindowData(windowID); if (!winData) { @@ -270,10 +263,6 @@ AudioChannelService::RegisterAudioChannelAgent(AudioChannelAgent* aAgent, void AudioChannelService::UnregisterAudioChannelAgent(AudioChannelAgent* aAgent) { - if (mDisabled) { - return; - } - AudioChannelWindow* winData = GetWindowData(aAgent->WindowID()); if (!winData) { return; @@ -463,49 +452,9 @@ AudioChannelService::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { if (!strcmp(aTopic, "xpcom-shutdown")) { - mDisabled = true; mWindows.Clear(); - } - -#ifdef MOZ_WIDGET_GONK - // To process the volume control on each audio channel according to - // change of settings - else if (!strcmp(aTopic, "mozsettings-changed")) { - RootedDictionary setting(nsContentUtils::RootingCxForThread()); - if (!WrappedJSToDictionary(aSubject, setting)) { - return NS_OK; - } - if (!StringBeginsWith(setting.mKey, NS_LITERAL_STRING("audio.volume."))) { - return NS_OK; - } - if (!setting.mValue.isNumber()) { - return NS_OK; - } - - nsCOMPtr audioManager = do_GetService(NS_AUDIOMANAGER_CONTRACTID); - NS_ENSURE_TRUE(audioManager, NS_OK); - - int32_t index = setting.mValue.toNumber(); - if (setting.mKey.EqualsLiteral("audio.volume.content")) { - audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Content, index); - } else if (setting.mKey.EqualsLiteral("audio.volume.notification")) { - audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Notification, index); - } else if (setting.mKey.EqualsLiteral("audio.volume.alarm")) { - audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Alarm, index); - } else if (setting.mKey.EqualsLiteral("audio.volume.telephony")) { - audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Telephony, index); - } else if (!setting.mKey.EqualsLiteral("audio.volume.bt_sco")) { - // bt_sco is not a valid audio channel so we manipulate it in - // AudioManager.cpp. And the others should not be used. - // We didn't use MOZ_CRASH or MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE here - // because any web content who has permission of mozSettings can set any - // names then it can be easy to crash the B2G. - NS_WARNING("unexpected audio channel for volume control"); - } - } -#endif - - else if (!strcmp(aTopic, "outer-window-destroyed")) { + Shutdown(); + } else if (!strcmp(aTopic, "outer-window-destroyed")) { nsCOMPtr wrapper = do_QueryInterface(aSubject); NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE); @@ -544,9 +493,7 @@ AudioChannelService::Observe(nsISupports* aSubject, const char* aTopic, mSpeakerManager[i]->SetAudioChannelActive(active); } #endif - } - - else if (!strcmp(aTopic, "ipc:content-shutdown")) { + } else if (!strcmp(aTopic, "ipc:content-shutdown")) { nsCOMPtr props = do_QueryInterface(aSubject); if (!props) { NS_WARNING("ipc:content-shutdown message without property bag as subject"); diff --git a/dom/audiochannel/AudioChannelService.h b/dom/audiochannel/AudioChannelService.h index 06dd5d5687..e28e89b21e 100644 --- a/dom/audiochannel/AudioChannelService.h +++ b/dom/audiochannel/AudioChannelService.h @@ -43,11 +43,6 @@ public: */ static already_AddRefed GetOrCreate(); - /** - * Shutdown the singleton. - */ - static void Shutdown(); - static bool IsAudioChannelMutedByDefault(); /** @@ -144,6 +139,11 @@ private: AudioChannelService(); ~AudioChannelService(); + /** + * Shutdown the singleton. + */ + static void Shutdown(); + void MaybeSendStatusUpdate(); bool ContentOrNormalChannelIsActive(); @@ -212,8 +212,6 @@ private: nsTArray mSpeakerManager; #endif - bool mDisabled; - nsCOMPtr mRunnable; uint64_t mDefChannelChildID; diff --git a/dom/audiochannel/nsIAudioChannelAgent.idl b/dom/audiochannel/nsIAudioChannelAgent.idl index f4ec17daeb..87934d4bac 100644 --- a/dom/audiochannel/nsIAudioChannelAgent.idl +++ b/dom/audiochannel/nsIAudioChannelAgent.idl @@ -34,7 +34,7 @@ interface nsIAudioChannelAgentCallback : nsISupports * 1. Changes to the playable status of this channel. */ -[uuid(e28e1569-2a44-4f71-9cd0-216874b05d57)] +[uuid(ee39a34b-a5c7-4b30-b1ac-cd64ceedef67)] interface nsIAudioChannelAgent : nsISupports { const long AUDIO_AGENT_CHANNEL_NORMAL = 0; @@ -44,6 +44,7 @@ interface nsIAudioChannelAgent : nsISupports const long AUDIO_AGENT_CHANNEL_TELEPHONY = 4; const long AUDIO_AGENT_CHANNEL_RINGER = 5; const long AUDIO_AGENT_CHANNEL_PUBLICNOTIFICATION = 6; + const long AUDIO_AGENT_CHANNEL_SYSTEM = 7; const long AUDIO_AGENT_CHANNEL_ERROR = 1000; diff --git a/dom/media/CubebUtils.cpp b/dom/media/CubebUtils.cpp index 4e0262b1d9..59e03ac102 100644 --- a/dom/media/CubebUtils.cpp +++ b/dom/media/CubebUtils.cpp @@ -181,6 +181,8 @@ cubeb_stream_type ConvertChannelToCubebType(dom::AudioChannel aChannel) return CUBEB_STREAM_TYPE_VOICE_CALL; case dom::AudioChannel::Ringer: return CUBEB_STREAM_TYPE_RING; + case dom::AudioChannel::System: + return CUBEB_STREAM_TYPE_SYSTEM; case dom::AudioChannel::Publicnotification: return CUBEB_STREAM_TYPE_SYSTEM_ENFORCED; default: diff --git a/dom/system/gonk/AudioManager.cpp b/dom/system/gonk/AudioManager.cpp index 5c1893a1c9..bd2c7e1e00 100644 --- a/dom/system/gonk/AudioManager.cpp +++ b/dom/system/gonk/AudioManager.cpp @@ -62,11 +62,12 @@ using namespace mozilla::dom::bluetooth; #define MOZ_SETTINGS_CHANGE_ID "mozsettings-changed" #define AUDIO_CHANNEL_PROCESS_CHANGED "audio-channel-process-changed" #define AUDIO_POLICY_SERVICE_NAME "media.audio_policy" +#define SETTINGS_SERVICE "@mozilla.org/settingsService;1" static void BinderDeadCallback(status_t aErr); static void InternalSetAudioRoutes(SwitchState aState); // Refer AudioService.java from Android -static int sMaxStreamVolumeTbl[AUDIO_STREAM_CNT] = { +static const uint32_t sMaxStreamVolumeTbl[AUDIO_STREAM_CNT] = { 5, // voice call 15, // system 15, // ring @@ -89,6 +90,47 @@ static bool sA2dpSwitchDone = true; namespace mozilla { namespace dom { namespace gonk { +static const VolumeData gVolumeData[VOLUME_TOTAL_NUMBER] = { + {"audio.volume.content", VOLUME_MEDIA}, + {"audio.volume.notification", VOLUME_NOTIFICATION}, + {"audio.volume.alarm", VOLUME_ALARM}, + {"audio.volume.telephony", VOLUME_TELEPHONY}, + {"audio.volume.bt_sco", VOLUME_BLUETOOTH_SCO} +}; + +class AudioProfileData final +{ +public: + explicit AudioProfileData(AudioOutputProfiles aProfile) + : mProfile(aProfile) + , mActive(false) + { + for (uint32_t idx = 0; idx < VOLUME_TOTAL_NUMBER; ++idx) { + mVolumeTable.AppendElement(0); + } + }; + + AudioOutputProfiles GetProfile() const + { + return mProfile; + } + + void SetActive(bool aActive) + { + mActive = aActive; + } + + bool GetActive() const + { + return mActive; + } + + nsTArray mVolumeTable; +private: + const AudioOutputProfiles mProfile; + bool mActive; +}; + class RecoverTask : public nsRunnable { public: @@ -98,7 +140,7 @@ public: NS_ENSURE_TRUE(amService, NS_OK); AudioManager *am = static_cast(amService.get()); - int attempt; + uint32_t attempt; for (attempt = 0; attempt < 50; attempt++) { if (defaultServiceManager()->checkService(String16(AUDIO_POLICY_SERVICE_NAME)) != 0) { break; @@ -110,10 +152,10 @@ public: MOZ_RELEASE_ASSERT(attempt < 50); - for (int loop = 0; loop < AUDIO_STREAM_CNT; loop++) { + for (uint32_t loop = 0; loop < AUDIO_STREAM_CNT; ++loop) { AudioSystem::initStreamVolume(static_cast(loop), 0, sMaxStreamVolumeTbl[loop]); - int32_t index; + uint32_t index; am->GetStreamVolumeIndex(loop, &index); am->SetStreamVolumeIndex(loop, index); } @@ -147,31 +189,22 @@ public: NS_IMETHOD Handle(const nsAString& aName, JS::Handle aResult) { - nsCOMPtr audioManager = - do_GetService(NS_AUDIOMANAGER_CONTRACTID); NS_ENSURE_TRUE(aResult.isInt32(), NS_OK); - - int32_t volIndex = aResult.toInt32(); - if (aName.EqualsLiteral("audio.volume.content")) { - audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Content, - volIndex); - } else if (aName.EqualsLiteral("audio.volume.notification")) { - audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Notification, - volIndex); - } else if (aName.EqualsLiteral("audio.volume.alarm")) { - audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Alarm, - volIndex); - } else if (aName.EqualsLiteral("audio.volume.telephony")) { - audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Telephony, - volIndex); - } else if (aName.EqualsLiteral("audio.volume.bt_sco")) { - static_cast(audioManager.get())->SetStreamVolumeIndex( - AUDIO_STREAM_BLUETOOTH_SCO, volIndex); - } else { - MOZ_ASSERT_UNREACHABLE("unexpected audio channel for initializing " - "volume control"); + nsRefPtr audioManager = AudioManager::GetInstance(); + MOZ_ASSERT(audioManager); + uint32_t volIndex = aResult.toInt32(); + for (uint32_t idx = 0; idx < VOLUME_TOTAL_NUMBER; ++idx) { + if (aName.EqualsASCII(gVolumeData[idx].mChannelName)) { + uint32_t category = gVolumeData[idx].mCategory; + nsresult rv = audioManager->ValidateVolumeIndex(category, volIndex); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + audioManager->InitProfilesVolume(gVolumeData[idx].mCategory, volIndex); + return NS_OK; + } } - + NS_WARNING("unexpected event name for initializing volume control"); return NS_OK; } @@ -294,17 +327,21 @@ AudioManager::HandleBluetoothStatusChanged(nsISupports* aSubject, cmd.appendFormat("bt_samplerate=%d", kBtSampleRate); AudioSystem::setParameters(0, cmd); SetForceForUse(nsIAudioManager::USE_COMMUNICATION, nsIAudioManager::FORCE_BT_SCO); + SwitchProfileData(DEVICE_BLUETOOTH, true); } else { int32_t force; GetForceForUse(nsIAudioManager::USE_COMMUNICATION, &force); - if (force == nsIAudioManager::FORCE_BT_SCO) + if (force == nsIAudioManager::FORCE_BT_SCO) { SetForceForUse(nsIAudioManager::USE_COMMUNICATION, nsIAudioManager::FORCE_NONE); + } + SwitchProfileData(DEVICE_BLUETOOTH, false); } } else if (!strcmp(aTopic, BLUETOOTH_A2DP_STATUS_CHANGED_ID)) { if (audioState == AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE && sA2dpSwitchDone) { MessageLoop::current()->PostDelayedTask( FROM_HERE, NewRunnableFunction(&ProcessDelayedA2dpRoute, audioState, aAddress), 1000); sA2dpSwitchDone = false; + SwitchProfileData(DEVICE_BLUETOOTH, false); } else { AudioSystem::setDeviceConnectionState(AUDIO_DEVICE_OUT_BLUETOOTH_A2DP, audioState, aAddress.get()); @@ -313,6 +350,7 @@ AudioManager::HandleBluetoothStatusChanged(nsISupports* aSubject, cmd.setTo("A2dpSuspended=false"); AudioSystem::setParameters(0, cmd); sA2dpSwitchDone = true; + SwitchProfileData(DEVICE_BLUETOOTH, true); #if ANDROID_VERSION >= 17 if (AudioSystem::getForceUse(AUDIO_POLICY_FORCE_FOR_MEDIA) == AUDIO_POLICY_FORCE_NO_BT_A2DP) { SetForceForUse(AUDIO_POLICY_FORCE_FOR_MEDIA, AUDIO_POLICY_FORCE_NONE); @@ -390,24 +428,27 @@ AudioManager::Observe(nsISupports* aSubject, return NS_OK; } - // To process the volume control on each audio channel according to + // To process the volume control on each volume categories according to // change of settings else if (!strcmp(aTopic, MOZ_SETTINGS_CHANGE_ID)) { RootedDictionary setting(nsContentUtils::RootingCxForThread()); if (!WrappedJSToDictionary(aSubject, setting)) { return NS_OK; } - if (!setting.mKey.EqualsASCII("audio.volume.bt_sco")) { + if (!StringBeginsWith(setting.mKey, NS_LITERAL_STRING("audio.volume."))) { return NS_OK; } if (!setting.mValue.isNumber()) { return NS_OK; } - int32_t index = setting.mValue.toNumber(); - SetStreamVolumeIndex(AUDIO_STREAM_BLUETOOTH_SCO, index); - - return NS_OK; + uint32_t volIndex = setting.mValue.toNumber(); + for (uint32_t idx = 0; idx < VOLUME_TOTAL_NUMBER; ++idx) { + if (setting.mKey.EqualsASCII(gVolumeData[idx].mChannelName)) { + SetVolumeByCategory(gVolumeData[idx].mCategory, volIndex); + return NS_OK; + } + } } NS_WARNING("Unexpected topic in AudioManager"); @@ -442,9 +483,11 @@ public: if (aEvent.status() == SWITCH_STATE_OFF && sSwitchDone) { MessageLoop::current()->PostDelayedTask( FROM_HERE, NewRunnableFunction(&ProcessDelayedAudioRoute, SWITCH_STATE_OFF), 1000); + mAudioManager->SwitchProfileData(DEVICE_HEADSET, false); sSwitchDone = false; } else if (aEvent.status() != SWITCH_STATE_OFF) { InternalSetAudioRoutes(aEvent.status()); + mAudioManager->SwitchProfileData(DEVICE_HEADSET, true); sSwitchDone = true; } // Handle the coexistence of a2dp / headset device, latest one wins. @@ -474,7 +517,7 @@ AudioManager::AudioManager() InternalSetAudioRoutes(GetCurrentSwitchState(SWITCH_HEADPHONES)); NotifyHeadphonesStatus(GetCurrentSwitchState(SWITCH_HEADPHONES)); - for (int loop = 0; loop < AUDIO_STREAM_CNT; loop++) { + for (uint32_t loop = 0; loop < AUDIO_STREAM_CNT; ++loop) { AudioSystem::initStreamVolume(static_cast(loop), 0, sMaxStreamVolumeTbl[loop]); mCurrentStreamVolumeTbl[loop] = sMaxStreamVolumeTbl[loop]; @@ -482,6 +525,7 @@ AudioManager::AudioManager() // Force publicnotification to output at maximal volume SetStreamVolumeIndex(AUDIO_STREAM_ENFORCED_AUDIBLE, sMaxStreamVolumeTbl[AUDIO_STREAM_ENFORCED_AUDIBLE]); + CreateAudioProfilesData(); // Get the initial volume index from settings DB during boot up. nsCOMPtr settingsService = @@ -492,11 +536,9 @@ AudioManager::AudioManager() NS_ENSURE_SUCCESS_VOID(rv); nsCOMPtr callback = new AudioChannelVolInitCallback(); NS_ENSURE_TRUE_VOID(callback); - lock->Get("audio.volume.content", callback); - lock->Get("audio.volume.notification", callback); - lock->Get("audio.volume.alarm", callback); - lock->Get("audio.volume.telephony", callback); - lock->Get("audio.volume.bt_sco", callback); + for (uint32_t idx = 0; idx < VOLUME_TOTAL_NUMBER; ++idx) { + lock->Get(gVolumeData[idx].mChannelName, callback); + } // Gecko only control stream volume not master so set to default value // directly. @@ -719,114 +761,207 @@ AudioManager::SetFmRadioAudioEnabled(bool aFmRadioAudioEnabled) InternalSetAudioRoutes(GetCurrentSwitchState(SWITCH_HEADPHONES)); // sync volume with music after powering on fm radio if (aFmRadioAudioEnabled) { - int32_t volIndex = mCurrentStreamVolumeTbl[AUDIO_STREAM_MUSIC]; + uint32_t volIndex = mCurrentStreamVolumeTbl[AUDIO_STREAM_MUSIC]; SetStreamVolumeIndex(AUDIO_STREAM_FM, volIndex); mCurrentStreamVolumeTbl[AUDIO_STREAM_FM] = volIndex; } return NS_OK; } -NS_IMETHODIMP -AudioManager::SetAudioChannelVolume(int32_t aChannel, int32_t aIndex) { - nsresult status; +nsresult +AudioManager::ValidateVolumeIndex(uint32_t aCategory, uint32_t aIndex) const +{ + uint32_t maxIndex = GetMaxVolumeByCategory(aCategory); + if (aIndex > maxIndex) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} - switch (static_cast(aChannel)) { - case AudioChannel::Content: +nsresult +AudioManager::SetVolumeByCategory(uint32_t aCategory, uint32_t aIndex) +{ + nsresult status; + switch (static_cast(aCategory)) { + case VOLUME_MEDIA: // sync FMRadio's volume with content channel. if (IsDeviceOn(AUDIO_DEVICE_OUT_FM)) { status = SetStreamVolumeIndex(AUDIO_STREAM_FM, aIndex); - NS_ENSURE_SUCCESS(status, status); + if (NS_WARN_IF(NS_FAILED(status))) { + return status; + } } status = SetStreamVolumeIndex(AUDIO_STREAM_MUSIC, aIndex); - NS_ENSURE_SUCCESS(status, status); + break; + case VOLUME_NOTIFICATION: + status = SetStreamVolumeIndex(AUDIO_STREAM_NOTIFICATION, aIndex); + if (NS_WARN_IF(NS_FAILED(status))) { + return status; + } + status = SetStreamVolumeIndex(AUDIO_STREAM_RING, aIndex); + if (NS_WARN_IF(NS_FAILED(status))) { + return status; + } status = SetStreamVolumeIndex(AUDIO_STREAM_SYSTEM, aIndex); break; - case AudioChannel::Notification: - status = SetStreamVolumeIndex(AUDIO_STREAM_NOTIFICATION, aIndex); - NS_ENSURE_SUCCESS(status, status); - status = SetStreamVolumeIndex(AUDIO_STREAM_RING, aIndex); - break; - case AudioChannel::Alarm: + case VOLUME_ALARM: status = SetStreamVolumeIndex(AUDIO_STREAM_ALARM, aIndex); break; - case AudioChannel::Telephony: + case VOLUME_TELEPHONY: status = SetStreamVolumeIndex(AUDIO_STREAM_VOICE_CALL, aIndex); + case VOLUME_BLUETOOTH_SCO: + status = SetStreamVolumeIndex(AUDIO_STREAM_BLUETOOTH_SCO, aIndex); break; default: return NS_ERROR_INVALID_ARG; } + return status; +} +uint32_t +AudioManager::GetVolumeByCategory(uint32_t aCategory) const +{ + switch (static_cast(aCategory)) { + case VOLUME_MEDIA: + return mCurrentStreamVolumeTbl[AUDIO_STREAM_MUSIC]; + case VOLUME_NOTIFICATION: + MOZ_ASSERT(mCurrentStreamVolumeTbl[AUDIO_STREAM_NOTIFICATION] == + mCurrentStreamVolumeTbl[AUDIO_STREAM_RING]); + MOZ_ASSERT(mCurrentStreamVolumeTbl[AUDIO_STREAM_NOTIFICATION] == + mCurrentStreamVolumeTbl[AUDIO_STREAM_SYSTEM]); + return mCurrentStreamVolumeTbl[AUDIO_STREAM_NOTIFICATION]; + case VOLUME_ALARM: + return mCurrentStreamVolumeTbl[AUDIO_STREAM_ALARM]; + case VOLUME_TELEPHONY: + return mCurrentStreamVolumeTbl[AUDIO_STREAM_VOICE_CALL]; + case VOLUME_BLUETOOTH_SCO: + return mCurrentStreamVolumeTbl[AUDIO_STREAM_BLUETOOTH_SCO]; + default: + NS_WARNING("Can't get volume from error volume category."); + return 0; + } +} + +uint32_t +AudioManager::GetMaxVolumeByCategory(uint32_t aCategory) const +{ + switch (static_cast(aCategory)) { + case VOLUME_MEDIA: + return sMaxStreamVolumeTbl[AUDIO_STREAM_MUSIC]; + case VOLUME_NOTIFICATION: + MOZ_ASSERT(sMaxStreamVolumeTbl[AUDIO_STREAM_NOTIFICATION] == + sMaxStreamVolumeTbl[AUDIO_STREAM_RING]); + MOZ_ASSERT(sMaxStreamVolumeTbl[AUDIO_STREAM_NOTIFICATION] == + sMaxStreamVolumeTbl[AUDIO_STREAM_SYSTEM]); + return sMaxStreamVolumeTbl[AUDIO_STREAM_NOTIFICATION]; + case VOLUME_ALARM: + return sMaxStreamVolumeTbl[AUDIO_STREAM_ALARM]; + case VOLUME_TELEPHONY: + return sMaxStreamVolumeTbl[AUDIO_STREAM_VOICE_CALL]; + case VOLUME_BLUETOOTH_SCO: + return sMaxStreamVolumeTbl[AUDIO_STREAM_BLUETOOTH_SCO]; + default: + NS_WARNING("Can't get max volume from error volume category."); + return 0; + } +} + +NS_IMETHODIMP +AudioManager::SetAudioChannelVolume(uint32_t aChannel, uint32_t aIndex) +{ + nsresult status; + AudioVolumeCategories category = (mPresentProfile == DEVICE_BLUETOOTH) ? + VOLUME_BLUETOOTH_SCO : VOLUME_TELEPHONY; + switch (static_cast(aChannel)) { + case AudioChannel::Normal: + case AudioChannel::Content: + status = SetVolumeByCategory(VOLUME_MEDIA, aIndex); + break; + case AudioChannel::Notification: + case AudioChannel::Ringer: + case AudioChannel::Publicnotification: + case AudioChannel::System: + status = SetVolumeByCategory(VOLUME_NOTIFICATION, aIndex); + break; + case AudioChannel::Alarm: + status = SetVolumeByCategory(VOLUME_ALARM, aIndex); + break; + case AudioChannel::Telephony: + status = SetVolumeByCategory(category, aIndex); + break; + default: + return NS_ERROR_INVALID_ARG; + } return status; } NS_IMETHODIMP -AudioManager::GetAudioChannelVolume(int32_t aChannel, int32_t* aIndex) { +AudioManager::GetAudioChannelVolume(uint32_t aChannel, uint32_t* aIndex) +{ if (!aIndex) { return NS_ERROR_NULL_POINTER; } - + AudioVolumeCategories category = (mPresentProfile == DEVICE_BLUETOOTH) ? + VOLUME_BLUETOOTH_SCO : VOLUME_TELEPHONY; switch (static_cast(aChannel)) { + case AudioChannel::Normal: case AudioChannel::Content: - MOZ_ASSERT(mCurrentStreamVolumeTbl[AUDIO_STREAM_MUSIC] == - mCurrentStreamVolumeTbl[AUDIO_STREAM_SYSTEM]); - *aIndex = mCurrentStreamVolumeTbl[AUDIO_STREAM_MUSIC]; + *aIndex = GetVolumeByCategory(VOLUME_MEDIA); break; case AudioChannel::Notification: - MOZ_ASSERT(mCurrentStreamVolumeTbl[AUDIO_STREAM_NOTIFICATION] == - mCurrentStreamVolumeTbl[AUDIO_STREAM_RING]); - *aIndex = mCurrentStreamVolumeTbl[AUDIO_STREAM_NOTIFICATION]; + case AudioChannel::Ringer: + case AudioChannel::Publicnotification: + case AudioChannel::System: + *aIndex = GetVolumeByCategory(VOLUME_NOTIFICATION); break; case AudioChannel::Alarm: - *aIndex = mCurrentStreamVolumeTbl[AUDIO_STREAM_ALARM]; + *aIndex = GetVolumeByCategory(VOLUME_ALARM); break; case AudioChannel::Telephony: - *aIndex = mCurrentStreamVolumeTbl[AUDIO_STREAM_VOICE_CALL]; + *aIndex = GetVolumeByCategory(category); break; default: return NS_ERROR_INVALID_ARG; } - return NS_OK; } NS_IMETHODIMP -AudioManager::GetMaxAudioChannelVolume(int32_t aChannel, int32_t* aMaxIndex) { +AudioManager::GetMaxAudioChannelVolume(uint32_t aChannel, uint32_t* aMaxIndex) +{ if (!aMaxIndex) { return NS_ERROR_NULL_POINTER; } - - int32_t stream; + AudioVolumeCategories category = (mPresentProfile == DEVICE_BLUETOOTH) ? + VOLUME_BLUETOOTH_SCO : VOLUME_TELEPHONY; switch (static_cast(aChannel)) { + case AudioChannel::Normal: case AudioChannel::Content: - MOZ_ASSERT(sMaxStreamVolumeTbl[AUDIO_STREAM_MUSIC] == - sMaxStreamVolumeTbl[AUDIO_STREAM_SYSTEM]); - stream = AUDIO_STREAM_MUSIC; + *aMaxIndex = GetMaxVolumeByCategory(VOLUME_MEDIA); break; case AudioChannel::Notification: - MOZ_ASSERT(sMaxStreamVolumeTbl[AUDIO_STREAM_NOTIFICATION] == - sMaxStreamVolumeTbl[AUDIO_STREAM_RING]); - stream = AUDIO_STREAM_NOTIFICATION; + case AudioChannel::Ringer: + case AudioChannel::Publicnotification: + case AudioChannel::System: + *aMaxIndex = GetMaxVolumeByCategory(VOLUME_NOTIFICATION); break; case AudioChannel::Alarm: - stream = AUDIO_STREAM_ALARM; + *aMaxIndex = GetMaxVolumeByCategory(VOLUME_ALARM); break; case AudioChannel::Telephony: - stream = AUDIO_STREAM_VOICE_CALL; + *aMaxIndex = GetMaxVolumeByCategory(category); break; default: return NS_ERROR_INVALID_ARG; } - - *aMaxIndex = sMaxStreamVolumeTbl[stream]; - return NS_OK; + return NS_OK; } nsresult -AudioManager::SetStreamVolumeIndex(int32_t aStream, int32_t aIndex) { - if (aIndex < 0 || aIndex > sMaxStreamVolumeTbl[aStream]) { +AudioManager::SetStreamVolumeIndex(int32_t aStream, uint32_t aIndex) { + if (aIndex > sMaxStreamVolumeTbl[aStream]) { return NS_ERROR_INVALID_ARG; } - mCurrentStreamVolumeTbl[aStream] = aIndex; status_t status; #if ANDROID_VERSION < 17 @@ -835,53 +970,50 @@ AudioManager::SetStreamVolumeIndex(int32_t aStream, int32_t aIndex) { aIndex); return status ? NS_ERROR_FAILURE : NS_OK; #else - int device = 0; - - if (aStream == AUDIO_STREAM_BLUETOOTH_SCO) { - device = AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET; - } else if (aStream == AUDIO_STREAM_FM) { - device = AUDIO_DEVICE_OUT_FM; - } - - if (device != 0) { + if (aStream == AUDIO_STREAM_FM) { status = AudioSystem::setStreamVolumeIndex( static_cast(aStream), aIndex, - device); + AUDIO_DEVICE_OUT_FM); return status ? NS_ERROR_FAILURE : NS_OK; } - status = AudioSystem::setStreamVolumeIndex( - static_cast(aStream), - aIndex, - AUDIO_DEVICE_OUT_BLUETOOTH_A2DP); - status += AudioSystem::setStreamVolumeIndex( - static_cast(aStream), - aIndex, - AUDIO_DEVICE_OUT_SPEAKER); - status += AudioSystem::setStreamVolumeIndex( + if (mPresentProfile == DEVICE_PRIMARY) { + status = AudioSystem::setStreamVolumeIndex( + static_cast(aStream), + aIndex, + AUDIO_DEVICE_OUT_SPEAKER); + status += AudioSystem::setStreamVolumeIndex( + static_cast(aStream), + aIndex, + AUDIO_DEVICE_OUT_EARPIECE); + } else if (mPresentProfile == DEVICE_HEADSET) { + status = AudioSystem::setStreamVolumeIndex( static_cast(aStream), aIndex, AUDIO_DEVICE_OUT_WIRED_HEADSET); - status += AudioSystem::setStreamVolumeIndex( + status += AudioSystem::setStreamVolumeIndex( static_cast(aStream), aIndex, AUDIO_DEVICE_OUT_WIRED_HEADPHONE); - status += AudioSystem::setStreamVolumeIndex( - static_cast(aStream), - aIndex, - AUDIO_DEVICE_OUT_EARPIECE); - status += AudioSystem::setStreamVolumeIndex( + } else if (mPresentProfile == DEVICE_BLUETOOTH) { + status = AudioSystem::setStreamVolumeIndex( + static_cast(aStream), + aIndex, + AUDIO_DEVICE_OUT_BLUETOOTH_A2DP); + status += AudioSystem::setStreamVolumeIndex( static_cast(aStream), aIndex, AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET); - + } else { + NS_WARNING("Can't set stream volume on error profile!"); + } return status ? NS_ERROR_FAILURE : NS_OK; #endif } nsresult -AudioManager::GetStreamVolumeIndex(int32_t aStream, int32_t *aIndex) { +AudioManager::GetStreamVolumeIndex(int32_t aStream, uint32_t *aIndex) { if (!aIndex) { return NS_ERROR_INVALID_ARG; } @@ -894,3 +1026,138 @@ AudioManager::GetStreamVolumeIndex(int32_t aStream, int32_t *aIndex) { return NS_OK; } + +AudioProfileData* +AudioManager::FindAudioProfileData(AudioOutputProfiles aProfile) +{ + uint32_t profilesNum = mAudioProfiles.Length(); + MOZ_ASSERT(profilesNum == DEVICE_TOTAL_NUMBER, "Error profile numbers!"); + for (uint32_t idx = 0; idx < profilesNum; ++idx) { + if (mAudioProfiles[idx]->GetProfile() == aProfile) { + return mAudioProfiles[idx]; + } + } + NS_WARNING("Can't find audio profile data"); + return nullptr; +} + +void +AudioManager::SendVolumeChangeNotification(AudioProfileData* aProfileData) +{ + MOZ_ASSERT(aProfileData); + nsresult rv; + nsCOMPtr service = do_GetService(SETTINGS_SERVICE, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + nsCOMPtr lock; + rv = service->CreateLock(nullptr, getter_AddRefs(lock)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + // Send events to update the Gaia volume + mozilla::AutoSafeJSContext cx; + JS::Rooted value(cx); + for (uint32_t idx = 0; idx < VOLUME_TOTAL_NUMBER; ++idx) { + value.setInt32(aProfileData->mVolumeTable[gVolumeData[idx].mCategory]); + lock->Set(gVolumeData[idx].mChannelName, value, nullptr, nullptr); + } +} + +void +AudioManager::CreateAudioProfilesData() +{ + MOZ_ASSERT(mAudioProfiles.IsEmpty(), "mAudioProfiles should be empty!"); + for (uint32_t idx = 0; idx < DEVICE_TOTAL_NUMBER; ++idx) { + AudioProfileData* profile = new AudioProfileData(static_cast(idx)); + mAudioProfiles.AppendElement(profile); + } + UpdateProfileState(DEVICE_PRIMARY, true); +} + +void +AudioManager::InitProfilesVolume(uint32_t aCategory, uint32_t aIndex) +{ + uint32_t profilesNum = mAudioProfiles.Length(); + MOZ_ASSERT(profilesNum == DEVICE_TOTAL_NUMBER, "Error profile numbers!"); + for (uint32_t idx = 0; idx < profilesNum; ++idx) { + mAudioProfiles[idx]->mVolumeTable[aCategory] = aIndex; + } + SetVolumeByCategory(aCategory, aIndex); +} + +void +AudioManager::SwitchProfileData(AudioOutputProfiles aProfile, + bool aActive) +{ + MOZ_ASSERT(DEVICE_PRIMARY <= aProfile && + aProfile < DEVICE_TOTAL_NUMBER, "Error profile type!"); + + // Save the present profile volume data. + AudioOutputProfiles oldProfile = mPresentProfile; + AudioProfileData* profileData = FindAudioProfileData(oldProfile); + MOZ_ASSERT(profileData); + UpdateVolumeToProfile(profileData); + UpdateProfileState(aProfile, aActive); + + AudioOutputProfiles newProfile = mPresentProfile; + if (oldProfile == newProfile) { + return; + } + + // Update new profile volume data and send the changing event. + profileData = FindAudioProfileData(newProfile); + MOZ_ASSERT(profileData); + UpdateVolumeFromProfile(profileData); + SendVolumeChangeNotification(profileData); +} + +void +AudioManager::UpdateProfileState(AudioOutputProfiles aProfile, bool aActive) +{ + MOZ_ASSERT(DEVICE_PRIMARY <= aProfile && aProfile < DEVICE_TOTAL_NUMBER, + "Error profile type!"); + if (aProfile == DEVICE_PRIMARY && !aActive) { + NS_WARNING("Can't turn off the primary profile!"); + return; + } + + mAudioProfiles[aProfile]->SetActive(aActive); + if (aActive) { + mPresentProfile = aProfile; + return; + } + + // The primary profile has the lowest priority. We will check whether there + // are other profiles. The bluetooth and headset have the same priotity. + uint32_t profilesNum = mAudioProfiles.Length(); + MOZ_ASSERT(profilesNum == DEVICE_TOTAL_NUMBER, "Error profile numbers!"); + for (int32_t idx = profilesNum - 1; idx >= 0; --idx) { + if (mAudioProfiles[idx]->GetActive()) { + mPresentProfile = static_cast(idx); + break; + } + } +} + +void +AudioManager::UpdateVolumeToProfile(AudioProfileData* aProfileData) +{ + MOZ_ASSERT(aProfileData); + for (uint32_t idx = 0; idx < VOLUME_TOTAL_NUMBER; ++idx) { + uint32_t volume = GetVolumeByCategory(gVolumeData[idx].mCategory); + aProfileData->mVolumeTable[gVolumeData[idx].mCategory] = volume; + } +} + +void +AudioManager::UpdateVolumeFromProfile(AudioProfileData* aProfileData) +{ + MOZ_ASSERT(aProfileData); + for (uint32_t idx = 0; idx < VOLUME_TOTAL_NUMBER; ++idx) { + SetVolumeByCategory(gVolumeData[idx].mCategory, + aProfileData->mVolumeTable[gVolumeData[idx].mCategory]); + } +} diff --git a/dom/system/gonk/AudioManager.h b/dom/system/gonk/AudioManager.h index 02fc94ea8f..9cac04682a 100644 --- a/dom/system/gonk/AudioManager.h +++ b/dom/system/gonk/AudioManager.h @@ -36,8 +36,47 @@ typedef Observer SwitchObserver; namespace dom { namespace gonk { + +/** + * FxOS can remeber the separate volume settings on difference output profiles. + * (1) Primary : speaker, receiver + * (2) Headset : wired headphone/headset + * (3) Bluetooth : BT SCO/A2DP devices + **/ +enum AudioOutputProfiles { + DEVICE_PRIMARY = 0, + DEVICE_HEADSET = 1, + DEVICE_BLUETOOTH = 2, + DEVICE_TOTAL_NUMBER = 3, +}; + +/** + * We have five sound volume settings from UX spec, + * You can see more informations in Bug1068219. + * (1) Media : music, video, FM ... + * (2) Notification : ringer, notification ... + * (3) Alarm : alarm + * (4) Telephony : GSM call, WebRTC call + * (5) Bluetooth SCO : SCO call + **/ +enum AudioVolumeCategories { + VOLUME_MEDIA = 0, + VOLUME_NOTIFICATION = 1, + VOLUME_ALARM = 2, + VOLUME_TELEPHONY = 3, + VOLUME_BLUETOOTH_SCO = 4, + VOLUME_TOTAL_NUMBER = 5, +}; + +struct VolumeData { + const char* mChannelName; + uint32_t mCategory; +}; + class RecoverTask; class AudioChannelVolInitCallback; +class AudioProfileData; + class AudioManager final : public nsIAudioManager , public nsIObserver { @@ -53,12 +92,18 @@ public: friend class RecoverTask; friend class AudioChannelVolInitCallback; + // Open or close the specific profile + void SwitchProfileData(AudioOutputProfiles aProfile, bool aActive); + + // Validate whether the volume index is within the range + nsresult ValidateVolumeIndex(uint32_t aCategory, uint32_t aIndex) const; + protected: int32_t mPhoneState; - int mCurrentStreamVolumeTbl[AUDIO_STREAM_CNT]; + uint32_t mCurrentStreamVolumeTbl[AUDIO_STREAM_CNT]; - nsresult SetStreamVolumeIndex(int32_t aStream, int32_t aIndex); - nsresult GetStreamVolumeIndex(int32_t aStream, int32_t *aIndex); + nsresult SetStreamVolumeIndex(int32_t aStream, uint32_t aIndex); + nsresult GetStreamVolumeIndex(int32_t aStream, uint32_t *aIndex); private: nsAutoPtr mObserver; @@ -68,12 +113,38 @@ private: // mIsMicMuted is only used for toggling mute call to RIL. bool mIsMicMuted; #endif + nsTArray> mAudioProfiles; + AudioOutputProfiles mPresentProfile; void HandleBluetoothStatusChanged(nsISupports* aSubject, const char* aTopic, const nsCString aAddress); void HandleAudioChannelProcessChanged(); + void CreateAudioProfilesData(); + + // Init the volume setting from the init setting callback + void InitProfilesVolume(uint32_t aCatogory, uint32_t aIndex); + + // Update volume data of profiles + void UpdateVolumeToProfile(AudioProfileData* aProfileData); + + // Apply the volume data to device + void UpdateVolumeFromProfile(AudioProfileData* aProfileData); + + // Send the volume changing event to Gaia + void SendVolumeChangeNotification(AudioProfileData* aProfileData); + + // Update the mPresentProfile and profiles active status + void UpdateProfileState(AudioOutputProfiles aProfile, bool aActive); + + // Volume control functions + nsresult SetVolumeByCategory(uint32_t aCategory, uint32_t aIndex); + uint32_t GetVolumeByCategory(uint32_t aCategory) const; + uint32_t GetMaxVolumeByCategory(uint32_t aCategory) const; + + AudioProfileData* FindAudioProfileData(AudioOutputProfiles aProfile); + AudioManager(); ~AudioManager(); }; diff --git a/dom/system/gonk/android_audio/AudioSystem.h b/dom/system/gonk/android_audio/AudioSystem.h index 7282baf056..2a6fc925d1 100644 --- a/dom/system/gonk/android_audio/AudioSystem.h +++ b/dom/system/gonk/android_audio/AudioSystem.h @@ -239,7 +239,7 @@ typedef enum { #if ANDROID_VERSION < 17 typedef enum { - /* output devices */ + /* output devices */ AUDIO_DEVICE_OUT_EARPIECE = 0x1, AUDIO_DEVICE_OUT_SPEAKER = 0x2, AUDIO_DEVICE_OUT_WIRED_HEADSET = 0x4, @@ -299,7 +299,7 @@ typedef enum { AUDIO_DEVICE_IN_FM_RX = 0x20000000, AUDIO_DEVICE_IN_FM_RX_A2DP = 0x40000000, AUDIO_DEVICE_IN_DEFAULT = 0x80000000, - + AUDIO_DEVICE_IN_ALL = (AUDIO_DEVICE_IN_COMMUNICATION | AUDIO_DEVICE_IN_AMBIENT | AUDIO_DEVICE_IN_BUILTIN_MIC | @@ -550,7 +550,7 @@ typedef uint32_t audio_devices_t; typedef enum { AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE, AUDIO_POLICY_DEVICE_STATE_AVAILABLE, - + AUDIO_POLICY_DEVICE_STATE_CNT, AUDIO_POLICY_DEVICE_STATE_MAX = AUDIO_POLICY_DEVICE_STATE_CNT - 1, } audio_policy_dev_state_t; diff --git a/dom/system/gonk/nsIAudioManager.idl b/dom/system/gonk/nsIAudioManager.idl index b417102506..206b248056 100644 --- a/dom/system/gonk/nsIAudioManager.idl +++ b/dom/system/gonk/nsIAudioManager.idl @@ -4,7 +4,7 @@ #include "nsISupports.idl" -[scriptable, builtinclass, uuid(60da41b4-cdc2-11e2-8a91-10bf48d64bd4)] +[scriptable, builtinclass, uuid(df31c280-1ef1-11e5-867f-0800200c9a66)] interface nsIAudioManager : nsISupports { /** @@ -52,8 +52,12 @@ interface nsIAudioManager : nsISupports void setForceForUse(in long usage, in long force); long getForceForUse(in long usage); - /* The range of volume index is from 0 to N. Ex: 0 ~ 15 */ - void setAudioChannelVolume(in long channel, in long index); - long getAudioChannelVolume(in long channel); - long getMaxAudioChannelVolume(in long channel); + /** + * These functions would be used when we enable the new volume control API + * (mozAudioChannelManager). The range of volume index is from 0 to N. + * More details on : https://gist.github.com/evanxd/41d8e2d91c5201a42bfa + */ + void setAudioChannelVolume(in unsigned long channel, in unsigned long index); + unsigned long getAudioChannelVolume(in unsigned long channel); + unsigned long getMaxAudioChannelVolume(in unsigned long channel); }; diff --git a/dom/webidl/AudioChannel.webidl b/dom/webidl/AudioChannel.webidl index 215ec0b65b..6e0ccf0b93 100644 --- a/dom/webidl/AudioChannel.webidl +++ b/dom/webidl/AudioChannel.webidl @@ -46,4 +46,5 @@ enum AudioChannel { "telephony", "ringer", "publicnotification", + "system" }; diff --git a/layout/build/nsLayoutStatics.cpp b/layout/build/nsLayoutStatics.cpp index 268b59cf13..94fe2a4215 100644 --- a/layout/build/nsLayoutStatics.cpp +++ b/layout/build/nsLayoutStatics.cpp @@ -427,8 +427,6 @@ nsLayoutStatics::Shutdown() nsHyphenationManager::Shutdown(); nsDOMMutationObserver::Shutdown(); - AudioChannelService::Shutdown(); - DataStoreService::Shutdown(); ContentParent::ShutDown();