author | Alastor Wu <alwu@mozilla.com> |
Mon, 17 Aug 2015 19:38:39 +0800 | |
changeset 258216 | 9bff295ea40376ede91bc90cd13593c04698b2b3 |
parent 258215 | ee800c0e234fe61cefd1e98f16f92ffc2bf8a94a |
child 258217 | 4e445d0cc426bce626352cfac5e0eb4e713f4c20 |
push id | 29246 |
push user | kwierso@gmail.com |
push date | Tue, 18 Aug 2015 22:23:21 +0000 |
treeherder | mozilla-central@db4616cd0721 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | baku |
bugs | 1179181 |
milestone | 43.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
dom/system/gonk/AudioManager.cpp | file | annotate | diff | comparison | revisions | |
dom/system/gonk/AudioManager.h | file | annotate | diff | comparison | revisions |
--- a/dom/system/gonk/AudioManager.cpp +++ b/dom/system/gonk/AudioManager.cpp @@ -26,16 +26,17 @@ #endif #include "nsISettingsService.h" #include "nsPrintfCString.h" #include "mozilla/Hal.h" #include "mozilla/Services.h" #include "mozilla/StaticPtr.h" #include "mozilla/ClearOnShutdown.h" +#include "mozilla/MozPromise.h" #include "mozilla/dom/ScriptSettings.h" #include "base/message_loop.h" #include "BluetoothCommon.h" #include "BluetoothHfpManagerBase.h" #include "nsJSUtils.h" #include "nsThreadUtils.h" @@ -75,27 +76,47 @@ static const uint32_t sMaxStreamVolumeTb 15, // alarm 15, // notification 15, // BT SCO 15, // enforced audible 15, // DTMF 15, // TTS 15, // FM }; + +// Use a half value of each volume category as the default volume. +static const uint32_t sDefaultVolumeCategoriesTbl[VOLUME_TOTAL_NUMBER] = { + 8, // VOLUME_MEDIA + 8, // VOLUME_NOTIFICATION + 8, // VOLUME_ALARM + 3, // VOLUME_TELEPHONY + 8, // VOLUME_BLUETOOTH_SCO +}; + +// Mappings AudioOutputProfiles to strings. +static const nsAttrValue::EnumTable kAudioOutputProfilesTable[] = { + { "primary", DEVICE_PRIMARY }, + { "headset", DEVICE_HEADSET }, + { "bluetooth", DEVICE_BLUETOOTH }, + { nullptr } +}; + // A bitwise variable for recording what kind of headset is attached. static int sHeadsetState; #if defined(MOZ_B2G_BT) || ANDROID_VERSION >= 17 static bool sBluetoothA2dpEnabled; #endif static const int kBtSampleRate = 8000; static bool sSwitchDone = true; #ifdef MOZ_B2G_BT static bool sA2dpSwitchDone = true; #endif +typedef MozPromise<bool, const char*, true> VolumeInitPromise; + 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}, @@ -179,56 +200,88 @@ public: AudioSystem::setPhoneState(static_cast<audio_mode_t>(phoneState)); #endif AudioSystem::get_audio_flinger(); return NS_OK; } }; -class AudioChannelVolInitCallback final : public nsISettingsServiceCallback +class VolumeInitCallback final : public nsISettingsServiceCallback { public: NS_DECL_ISUPPORTS - AudioChannelVolInitCallback() {} + VolumeInitCallback() + : mInitCounter(0) + { + mPromise = mPromiseHolder.Ensure(__func__); + } + + nsRefPtr<VolumeInitPromise> GetPromise() const + { + return mPromise; + } NS_IMETHOD Handle(const nsAString& aName, JS::Handle<JS::Value> aResult) { - NS_ENSURE_TRUE(aResult.isInt32(), NS_OK); nsRefPtr<AudioManager> 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)) { + NS_ConvertASCIItoUTF16 volumeType(gVolumeData[idx].mChannelName); + if (StringBeginsWith(aName, volumeType)) { + AudioOutputProfiles profile = GetProfileFromSettingName(aName); + MOZ_ASSERT(profile != DEVICE_ERROR); + uint32_t category = gVolumeData[idx].mCategory; + uint32_t volIndex = aResult.isInt32() ? + aResult.toInt32() : sDefaultVolumeCategoriesTbl[category]; nsresult rv = audioManager->ValidateVolumeIndex(category, volIndex); if (NS_WARN_IF(NS_FAILED(rv))) { + mPromiseHolder.Reject("Error : invalid volume index.", __func__); return rv; } - audioManager->InitProfilesVolume(gVolumeData[idx].mCategory, volIndex); + + audioManager->InitProfileVolume(profile, category, volIndex); + if (++mInitCounter == DEVICE_TOTAL_NUMBER * VOLUME_TOTAL_NUMBER) { + mPromiseHolder.Resolve(true, __func__); + } return NS_OK; } } - NS_WARNING("unexpected event name for initializing volume control"); + mPromiseHolder.Reject("Error : unexpected audio init event.", __func__); return NS_OK; } NS_IMETHOD HandleError(const nsAString& aName) { - LOG("AudioChannelVolInitCallback::HandleError: %s\n", - NS_ConvertUTF16toUTF8(aName).get()); + mPromiseHolder.Reject(NS_ConvertUTF16toUTF8(aName).get(), __func__); return NS_OK; } protected: - ~AudioChannelVolInitCallback() {} + ~VolumeInitCallback() {} + + AudioOutputProfiles GetProfileFromSettingName(const nsAString& aName) const + { + for (uint32_t idx = 0; kAudioOutputProfilesTable[idx].tag; ++idx) { + NS_ConvertASCIItoUTF16 profile(kAudioOutputProfilesTable[idx].tag); + if (StringEndsWith(aName, profile)) { + return static_cast<AudioOutputProfiles>(kAudioOutputProfilesTable[idx].value); + } + } + return DEVICE_ERROR; + } + + nsRefPtr<VolumeInitPromise> mPromise; + MozPromiseHolder<VolumeInitPromise> mPromiseHolder; + uint32_t mInitCounter; }; -NS_IMPL_ISUPPORTS(AudioChannelVolInitCallback, nsISettingsServiceCallback) +NS_IMPL_ISUPPORTS(VolumeInitCallback, nsISettingsServiceCallback) } /* namespace gonk */ } /* namespace dom */ } /* namespace mozilla */ static void BinderDeadCallback(status_t aErr) { if (aErr == DEAD_OBJECT) { @@ -532,27 +585,17 @@ AudioManager::AudioManager() mCurrentStreamVolumeTbl[loop] = sMaxStreamVolumeTbl[loop]; } // 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<nsISettingsService> settingsService = - do_GetService("@mozilla.org/settingsService;1"); - NS_ENSURE_TRUE_VOID(settingsService); - nsCOMPtr<nsISettingsServiceLock> lock; - nsresult rv = settingsService->CreateLock(nullptr, getter_AddRefs(lock)); - NS_ENSURE_SUCCESS_VOID(rv); - nsCOMPtr<nsISettingsServiceCallback> callback = new AudioChannelVolInitCallback(); - NS_ENSURE_TRUE_VOID(callback); - for (uint32_t idx = 0; idx < VOLUME_TOTAL_NUMBER; ++idx) { - lock->Get(gVolumeData[idx].mChannelName, callback); - } + InitVolumeFromDatabase(); // Gecko only control stream volume not master so set to default value // directly. AudioSystem::setMasterVolume(1.0); AudioSystem::setErrorCallback(BinderDeadCallback); nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); NS_ENSURE_TRUE_VOID(obs); @@ -602,16 +645,19 @@ AudioManager::~AudioManager() { NS_WARNING("Failed to remove bluetooth hfp NREC status changed observer!"); } if (NS_FAILED(obs->RemoveObserver(this, MOZ_SETTINGS_CHANGE_ID))) { NS_WARNING("Failed to remove mozsettings-changed observer!"); } if (NS_FAILED(obs->RemoveObserver(this, AUDIO_CHANNEL_PROCESS_CHANGED))) { NS_WARNING("Failed to remove audio-channel-process-changed!"); } + + // Store the present volume setting to setting database. + SendVolumeChangeNotification(FindAudioProfileData(mPresentProfile)); } static StaticRefPtr<AudioManager> sAudioManager; already_AddRefed<AudioManager> AudioManager::GetInstance() { // Avoid createing AudioManager from content process. @@ -1019,16 +1065,73 @@ AudioManager::FindAudioProfileData(Audio if (mAudioProfiles[idx]->GetProfile() == aProfile) { return mAudioProfiles[idx]; } } NS_WARNING("Can't find audio profile data"); return nullptr; } +nsAutoCString +AudioManager::AppendProfileToVolumeSetting(const char* aName, AudioOutputProfiles aProfile) +{ + nsAutoCString topic; + topic.Assign(aName); + for (uint32_t idx = 0; kAudioOutputProfilesTable[idx].tag; ++idx) { + if (kAudioOutputProfilesTable[idx].value == aProfile) { + topic.Append("."); + topic.Append(kAudioOutputProfilesTable[idx].tag); + break; + } + } + return topic; +} + + +void +AudioManager::InitVolumeFromDatabase() +{ + nsresult rv; + nsCOMPtr<nsISettingsService> service = do_GetService(SETTINGS_SERVICE, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + nsCOMPtr<nsISettingsServiceLock> lock; + rv = service->CreateLock(nullptr, getter_AddRefs(lock)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + nsRefPtr<VolumeInitCallback> callback = new VolumeInitCallback(); + MOZ_ASSERT(callback); + callback->GetPromise()->Then(AbstractThread::MainThread(), __func__, this, + &AudioManager::InitProfileVolumeSucceeded, + &AudioManager::InitProfileVolumeFailed); + + for (uint32_t idx = 0; idx < VOLUME_TOTAL_NUMBER; ++idx) { + for (uint32_t pIdx = 0; pIdx < DEVICE_TOTAL_NUMBER; ++pIdx) { + lock->Get(AppendProfileToVolumeSetting(gVolumeData[idx].mChannelName, + static_cast<AudioOutputProfiles>(pIdx)).get(), callback); + } + } +} + +void +AudioManager::InitProfileVolumeSucceeded() +{ + SendVolumeChangeNotification(FindAudioProfileData(mPresentProfile)); +} + +void +AudioManager::InitProfileVolumeFailed(const char* aError) +{ + NS_WARNING(aError); +} + void AudioManager::SendVolumeChangeNotification(AudioProfileData* aProfileData) { MOZ_ASSERT(aProfileData); nsresult rv; nsCOMPtr<nsISettingsService> service = do_GetService(SETTINGS_SERVICE, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return; @@ -1040,39 +1143,45 @@ AudioManager::SendVolumeChangeNotificati return; } // Send events to update the Gaia volume mozilla::AutoSafeJSContext cx; JS::Rooted<JS::Value> value(cx); for (uint32_t idx = 0; idx < VOLUME_TOTAL_NUMBER; ++idx) { value.setInt32(aProfileData->mVolumeTable[gVolumeData[idx].mCategory]); + // For reducing the code dependency, Gaia doesn't need to know the current + // profile, it only need to care about different volume categories. + // However, we need to send the setting volume to the permanent database, + // so that we can store the volume setting even if the phone reboots. lock->Set(gVolumeData[idx].mChannelName, value, nullptr, nullptr); + lock->Set(AppendProfileToVolumeSetting(gVolumeData[idx].mChannelName, + mPresentProfile).get(), 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<AudioOutputProfiles>(idx)); mAudioProfiles.AppendElement(profile); } UpdateProfileState(DEVICE_PRIMARY, true); } void -AudioManager::InitProfilesVolume(uint32_t aCategory, uint32_t aIndex) +AudioManager::InitProfileVolume(AudioOutputProfiles aProfile, + 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; - } + AudioProfileData* profileData = FindAudioProfileData(aProfile); + MOZ_ASSERT(profileData); + profileData->mVolumeTable[aCategory] = aIndex; SetVolumeByCategory(aCategory, aIndex); } void AudioManager::SwitchProfileData(AudioOutputProfiles aProfile, bool aActive) { MOZ_ASSERT(DEVICE_PRIMARY <= aProfile &&
--- a/dom/system/gonk/AudioManager.h +++ b/dom/system/gonk/AudioManager.h @@ -38,16 +38,17 @@ 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_ERROR = -1, DEVICE_PRIMARY = 0, DEVICE_HEADSET = 1, DEVICE_BLUETOOTH = 2, DEVICE_TOTAL_NUMBER = 3, }; /** * We have five sound volume settings from UX spec, @@ -68,33 +69,33 @@ enum AudioVolumeCategories { }; struct VolumeData { const char* mChannelName; uint32_t mCategory; }; class RecoverTask; -class AudioChannelVolInitCallback; +class VolumeInitCallback; class AudioProfileData; class AudioManager final : public nsIAudioManager , public nsIObserver { public: static already_AddRefed<AudioManager> GetInstance(); NS_DECL_ISUPPORTS NS_DECL_NSIAUDIOMANAGER NS_DECL_NSIOBSERVER // When audio backend is dead, recovery task needs to read all volume // settings then set back into audio backend. friend class RecoverTask; - friend class AudioChannelVolInitCallback; + friend class VolumeInitCallback; // 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: @@ -117,17 +118,18 @@ private: 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); + void InitProfileVolume(AudioOutputProfiles aProfile, + 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 @@ -138,16 +140,27 @@ private: // 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); + // Append the profile to the volume setting string. + nsAutoCString AppendProfileToVolumeSetting(const char* aName, + AudioOutputProfiles aProfile); + + // Init volume from the settings database. + void InitVolumeFromDatabase(); + + // Promise functions. + void InitProfileVolumeSucceeded(); + void InitProfileVolumeFailed(const char* aError); + AudioManager(); ~AudioManager(); }; } /* namespace gonk */ } /* namespace dom */ } /* namespace mozilla */