gecko-dev/dom/system/gonk/AudioManager.cpp
Hayden Huang 997857d190 Bug 1175447 - mono audio support. r=padenot, r=sotaro
This patch is to enable mono audio option for those who has full hearing loss in one ear.
With this feature, they can get the complete audio with using one ear.

--HG--
extra : transplant_source : %EC%27%97%1Ai%0D%E3%BC%D0%12%97-K%0Ek%BD%8A%C8%A9%85
2015-08-06 14:30:22 +08:00

1276 lines
41 KiB
C++

/* Copyright 2012 Mozilla Foundation and Mozilla contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <android/log.h>
#include <cutils/properties.h>
#include <binder/IServiceManager.h>
#include "AudioChannelService.h"
#include "AudioManager.h"
#include "nsIObserverService.h"
#ifdef MOZ_B2G_RIL
#include "nsIRadioInterfaceLayer.h"
#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"
#include "nsServiceManagerUtils.h"
#include "nsComponentManagerUtils.h"
#include "nsXULAppAPI.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/SettingChangeNotificationBinding.h"
using namespace mozilla::dom::gonk;
using namespace android;
using namespace mozilla::hal;
using namespace mozilla;
using namespace mozilla::dom::bluetooth;
#undef LOG
#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "AudioManager" , ## args)
#define HEADPHONES_STATUS_HEADSET MOZ_UTF16("headset")
#define HEADPHONES_STATUS_HEADPHONE MOZ_UTF16("headphone")
#define HEADPHONES_STATUS_OFF MOZ_UTF16("off")
#define HEADPHONES_STATUS_UNKNOWN MOZ_UTF16("unknown")
#define HEADPHONES_STATUS_CHANGED "headphones-status-changed"
#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"
// Refer AudioService.java from Android
static const uint32_t sMaxStreamVolumeTbl[AUDIO_STREAM_CNT] = {
5, // voice call
15, // system
15, // ring
15, // music
15, // alarm
15, // notification
15, // BT SCO
15, // enforced audible
15, // DTMF
15, // TTS
#if ANDROID_VERSION < 19
15, // FM
#endif
};
// 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 }
};
static const int kBtSampleRate = 8000;
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},
{"audio.volume.bt_sco", VOLUME_BLUETOOTH_SCO}
};
class RunnableCallTask : public Task
{
public:
explicit RunnableCallTask(nsIRunnable* aRunnable)
: mRunnable(aRunnable) {}
void Run() override
{
mRunnable->Run();
}
protected:
nsCOMPtr<nsIRunnable> mRunnable;
};
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<uint32_t> mVolumeTable;
private:
const AudioOutputProfiles mProfile;
bool mActive;
};
void
AudioManager::HandleAudioFlingerDied()
{
uint32_t attempt;
for (attempt = 0; attempt < 50; attempt++) {
if (defaultServiceManager()->checkService(String16(AUDIO_POLICY_SERVICE_NAME)) != 0) {
break;
}
LOG("AudioPolicyService is dead! attempt=%d", attempt);
usleep(1000 * 200);
}
MOZ_RELEASE_ASSERT(attempt < 50);
for (uint32_t loop = 0; loop < AUDIO_STREAM_CNT; ++loop) {
AudioSystem::initStreamVolume(static_cast<audio_stream_type_t>(loop),
0,
sMaxStreamVolumeTbl[loop]);
uint32_t index;
GetStreamVolumeIndex(loop, &index);
SetStreamVolumeIndex(loop, index);
}
if (mHeadsetState & AUDIO_DEVICE_OUT_WIRED_HEADSET) {
UpdateHeadsetConnectionState(SWITCH_STATE_HEADSET);
} else if (mHeadsetState & AUDIO_DEVICE_OUT_WIRED_HEADPHONE) {
UpdateHeadsetConnectionState(SWITCH_STATE_HEADPHONE);
} else {
UpdateHeadsetConnectionState(SWITCH_STATE_OFF);
}
int32_t phoneState = nsIAudioManager::PHONE_STATE_INVALID;
GetPhoneState(&phoneState);
#if ANDROID_VERSION < 17
AudioSystem::setPhoneState(phoneState);
#else
AudioSystem::setPhoneState(static_cast<audio_mode_t>(phoneState));
#endif
AudioSystem::get_audio_flinger();
}
class VolumeInitCallback final : public nsISettingsServiceCallback
{
public:
NS_DECL_ISUPPORTS
VolumeInitCallback()
: mInitCounter(0)
{
mPromise = mPromiseHolder.Ensure(__func__);
}
nsRefPtr<VolumeInitPromise> GetPromise() const
{
return mPromise;
}
NS_IMETHOD Handle(const nsAString& aName, JS::Handle<JS::Value> aResult)
{
nsRefPtr<AudioManager> audioManager = AudioManager::GetInstance();
MOZ_ASSERT(audioManager);
for (uint32_t idx = 0; idx < VOLUME_TOTAL_NUMBER; ++idx) {
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->InitProfileVolume(profile, category, volIndex);
if (++mInitCounter == DEVICE_TOTAL_NUMBER * VOLUME_TOTAL_NUMBER) {
mPromiseHolder.Resolve(true, __func__);
}
return NS_OK;
}
}
mPromiseHolder.Reject("Error : unexpected audio init event.", __func__);
return NS_OK;
}
NS_IMETHOD HandleError(const nsAString& aName)
{
mPromiseHolder.Reject(NS_ConvertUTF16toUTF8(aName).get(), __func__);
return NS_OK;
}
protected:
~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(VolumeInitCallback, nsISettingsServiceCallback)
static void
BinderDeadCallback(status_t aErr)
{
if (aErr != DEAD_OBJECT) {
return;
}
nsCOMPtr<nsIRunnable> runnable =
NS_NewRunnableFunction([]() {
MOZ_ASSERT(NS_IsMainThread());
nsRefPtr<AudioManager> audioManager = AudioManager::GetInstance();
NS_ENSURE_TRUE(audioManager.get(), );
audioManager->HandleAudioFlingerDied();
});
NS_DispatchToMainThread(runnable);
}
static bool
IsDeviceOn(audio_devices_t device)
{
#if ANDROID_VERSION >= 15
return AudioSystem::getDeviceConnectionState(device, "") ==
AUDIO_POLICY_DEVICE_STATE_AVAILABLE;
#else
return false;
#endif
}
NS_IMPL_ISUPPORTS(AudioManager, nsIAudioManager, nsIObserver)
void
AudioManager::UpdateHeadsetConnectionState(hal::SwitchState aState)
{
#if ANDROID_VERSION >= 15
if (aState == SWITCH_STATE_HEADSET) {
AudioSystem::setDeviceConnectionState(AUDIO_DEVICE_OUT_WIRED_HEADSET,
AUDIO_POLICY_DEVICE_STATE_AVAILABLE, "");
mHeadsetState |= AUDIO_DEVICE_OUT_WIRED_HEADSET;
} else if (aState == SWITCH_STATE_HEADPHONE) {
AudioSystem::setDeviceConnectionState(AUDIO_DEVICE_OUT_WIRED_HEADPHONE,
AUDIO_POLICY_DEVICE_STATE_AVAILABLE, "");
mHeadsetState |= AUDIO_DEVICE_OUT_WIRED_HEADPHONE;
} else if (aState == SWITCH_STATE_OFF) {
if (mHeadsetState & AUDIO_DEVICE_OUT_WIRED_HEADSET) {
AudioSystem::setDeviceConnectionState(AUDIO_DEVICE_OUT_WIRED_HEADSET,
AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE, "");
}
if (mHeadsetState & AUDIO_DEVICE_OUT_WIRED_HEADPHONE) {
AudioSystem::setDeviceConnectionState(AUDIO_DEVICE_OUT_WIRED_HEADPHONE,
AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE, "");
}
mHeadsetState = 0;
}
#else
NS_NOTREACHED("Doesn't support audio routing on GB version");
#endif
}
void
AudioManager::HandleBluetoothStatusChanged(nsISupports* aSubject,
const char* aTopic,
const nsCString aAddress)
{
#ifdef MOZ_B2G_BT
bool status;
if (!strcmp(aTopic, BLUETOOTH_SCO_STATUS_CHANGED_ID)) {
BluetoothHfpManagerBase* hfp =
static_cast<BluetoothHfpManagerBase*>(aSubject);
status = hfp->IsScoConnected();
} else {
BluetoothProfileManagerBase* profile =
static_cast<BluetoothProfileManagerBase*>(aSubject);
status = profile->IsConnected();
}
audio_policy_dev_state_t audioState = status ?
AUDIO_POLICY_DEVICE_STATE_AVAILABLE :
AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE;
if (!strcmp(aTopic, BLUETOOTH_SCO_STATUS_CHANGED_ID)) {
if (audioState == AUDIO_POLICY_DEVICE_STATE_AVAILABLE) {
String8 cmd;
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) {
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 && mA2dpSwitchDone) {
nsRefPtr<AudioManager> self = this;
nsCOMPtr<nsIRunnable> runnable =
NS_NewRunnableFunction([self, audioState, aAddress]() {
if (self->mA2dpSwitchDone) {
return;
}
AudioSystem::setDeviceConnectionState(AUDIO_DEVICE_OUT_BLUETOOTH_A2DP,
audioState,
aAddress.get());
String8 cmd("bluetooth_enabled=false");
AudioSystem::setParameters(0, cmd);
cmd.setTo("A2dpSuspended=true");
AudioSystem::setParameters(0, cmd);
self->mA2dpSwitchDone = true;
});
MessageLoop::current()->PostDelayedTask(
FROM_HERE, new RunnableCallTask(runnable), 1000);
mA2dpSwitchDone = false;
SwitchProfileData(DEVICE_BLUETOOTH, false);
} else {
AudioSystem::setDeviceConnectionState(AUDIO_DEVICE_OUT_BLUETOOTH_A2DP,
audioState, aAddress.get());
String8 cmd("bluetooth_enabled=true");
AudioSystem::setParameters(0, cmd);
cmd.setTo("A2dpSuspended=false");
AudioSystem::setParameters(0, cmd);
mA2dpSwitchDone = 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);
}
#endif
}
mBluetoothA2dpEnabled = audioState == AUDIO_POLICY_DEVICE_STATE_AVAILABLE;
} else if (!strcmp(aTopic, BLUETOOTH_HFP_STATUS_CHANGED_ID)) {
AudioSystem::setDeviceConnectionState(AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET,
audioState, aAddress.get());
AudioSystem::setDeviceConnectionState(AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET,
audioState, aAddress.get());
} else if (!strcmp(aTopic, BLUETOOTH_HFP_NREC_STATUS_CHANGED_ID)) {
String8 cmd;
BluetoothHfpManagerBase* hfp =
static_cast<BluetoothHfpManagerBase*>(aSubject);
if (hfp->IsNrecEnabled()) {
cmd.setTo("bt_headset_name=<unknown>;bt_headset_nrec=on");
AudioSystem::setParameters(0, cmd);
} else {
cmd.setTo("bt_headset_name=<unknown>;bt_headset_nrec=off");
AudioSystem::setParameters(0, cmd);
}
}
#endif
}
void
AudioManager::HandleAudioChannelProcessChanged()
{
// Note: If the user answers a VoIP call (e.g. WebRTC calls) during the
// telephony call (GSM/CDMA calls) the audio manager won't set the
// PHONE_STATE_IN_COMMUNICATION audio state. Once the telephony call finishes
// the RIL plumbing sets the PHONE_STATE_NORMAL audio state. This seems to be
// an issue for the VoIP call but it is not. Once the RIL plumbing sets the
// the PHONE_STATE_NORMAL audio state the AudioManager::mPhoneAudioAgent
// member will call the NotifyStoppedPlaying() method causing that this function will
// be called again and therefore the audio manager sets the
// PHONE_STATE_IN_COMMUNICATION audio state.
if ((mPhoneState == PHONE_STATE_IN_CALL) ||
(mPhoneState == PHONE_STATE_RINGTONE)) {
return;
}
nsRefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
MOZ_ASSERT(service);
bool telephonyChannelIsActive = service->TelephonyChannelIsActive();
telephonyChannelIsActive ? SetPhoneState(PHONE_STATE_IN_COMMUNICATION) :
SetPhoneState(PHONE_STATE_NORMAL);
}
nsresult
AudioManager::Observe(nsISupports* aSubject,
const char* aTopic,
const char16_t* aData)
{
if ((strcmp(aTopic, BLUETOOTH_SCO_STATUS_CHANGED_ID) == 0) ||
(strcmp(aTopic, BLUETOOTH_HFP_STATUS_CHANGED_ID) == 0) ||
(strcmp(aTopic, BLUETOOTH_HFP_NREC_STATUS_CHANGED_ID) == 0) ||
(strcmp(aTopic, BLUETOOTH_A2DP_STATUS_CHANGED_ID) == 0)) {
nsCString address = NS_ConvertUTF16toUTF8(nsDependentString(aData));
if (address.IsEmpty()) {
NS_WARNING(nsPrintfCString("Invalid address of %s", aTopic).get());
return NS_ERROR_FAILURE;
}
HandleBluetoothStatusChanged(aSubject, aTopic, address);
return NS_OK;
}
else if (!strcmp(aTopic, AUDIO_CHANNEL_PROCESS_CHANGED)) {
HandleAudioChannelProcessChanged();
return NS_OK;
}
// To process the volume control on each volume categories according to
// change of settings
else if (!strcmp(aTopic, MOZ_SETTINGS_CHANGE_ID)) {
RootedDictionary<dom::SettingChangeNotification> 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;
}
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");
return NS_ERROR_FAILURE;
}
static void
NotifyHeadphonesStatus(SwitchState aState)
{
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
if (aState == SWITCH_STATE_HEADSET) {
obs->NotifyObservers(nullptr, HEADPHONES_STATUS_CHANGED, HEADPHONES_STATUS_HEADSET);
} else if (aState == SWITCH_STATE_HEADPHONE) {
obs->NotifyObservers(nullptr, HEADPHONES_STATUS_CHANGED, HEADPHONES_STATUS_HEADPHONE);
} else if (aState == SWITCH_STATE_OFF) {
obs->NotifyObservers(nullptr, HEADPHONES_STATUS_CHANGED, HEADPHONES_STATUS_OFF);
} else {
obs->NotifyObservers(nullptr, HEADPHONES_STATUS_CHANGED, HEADPHONES_STATUS_UNKNOWN);
}
}
}
class HeadphoneSwitchObserver : public SwitchObserver
{
public:
void Notify(const hal::SwitchEvent& aEvent) {
nsRefPtr<AudioManager> audioManager = AudioManager::GetInstance();
MOZ_ASSERT(audioManager);
audioManager->HandleHeadphoneSwitchEvent(aEvent);
}
};
void
AudioManager::HandleHeadphoneSwitchEvent(const hal::SwitchEvent& aEvent)
{
NotifyHeadphonesStatus(aEvent.status());
// When user pulled out the headset, a delay of routing here can avoid the leakage of audio from speaker.
if (aEvent.status() == SWITCH_STATE_OFF && mSwitchDone) {
nsRefPtr<AudioManager> self = this;
nsCOMPtr<nsIRunnable> runnable =
NS_NewRunnableFunction([self]() {
if (self->mSwitchDone) {
return;
}
self->UpdateHeadsetConnectionState(SWITCH_STATE_OFF);
self->mSwitchDone = true;
});
MessageLoop::current()->PostDelayedTask(FROM_HERE, new RunnableCallTask(runnable), 1000);
SwitchProfileData(DEVICE_HEADSET, false);
mSwitchDone = false;
} else if (aEvent.status() != SWITCH_STATE_OFF) {
UpdateHeadsetConnectionState(aEvent.status());
SwitchProfileData(DEVICE_HEADSET, true);
mSwitchDone = true;
}
// Handle the coexistence of a2dp / headset device, latest one wins.
#if ANDROID_VERSION >= 17
int32_t forceUse = 0;
GetForceForUse(AUDIO_POLICY_FORCE_FOR_MEDIA, &forceUse);
if (aEvent.status() != SWITCH_STATE_OFF && mBluetoothA2dpEnabled) {
SetForceForUse(AUDIO_POLICY_FORCE_FOR_MEDIA, AUDIO_POLICY_FORCE_NO_BT_A2DP);
} else if (forceUse == AUDIO_POLICY_FORCE_NO_BT_A2DP) {
SetForceForUse(AUDIO_POLICY_FORCE_FOR_MEDIA, AUDIO_POLICY_FORCE_NONE);
}
#endif
}
AudioManager::AudioManager()
: mPhoneState(PHONE_STATE_CURRENT)
, mHeadsetState(0)
, mSwitchDone(true)
#if defined(MOZ_B2G_BT) || ANDROID_VERSION >= 17
, mBluetoothA2dpEnabled(false)
#endif
#ifdef MOZ_B2G_BT
, mA2dpSwitchDone(true)
#endif
, mObserver(new HeadphoneSwitchObserver())
#ifdef MOZ_B2G_RIL
, mMuteCallToRIL(false)
#endif
{
RegisterSwitchObserver(SWITCH_HEADPHONES, mObserver);
UpdateHeadsetConnectionState(GetCurrentSwitchState(SWITCH_HEADPHONES));
NotifyHeadphonesStatus(GetCurrentSwitchState(SWITCH_HEADPHONES));
for (uint32_t loop = 0; loop < AUDIO_STREAM_CNT; ++loop) {
AudioSystem::initStreamVolume(static_cast<audio_stream_type_t>(loop), 0,
sMaxStreamVolumeTbl[loop]);
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.
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);
if (NS_FAILED(obs->AddObserver(this, BLUETOOTH_SCO_STATUS_CHANGED_ID, false))) {
NS_WARNING("Failed to add bluetooth sco status changed observer!");
}
if (NS_FAILED(obs->AddObserver(this, BLUETOOTH_A2DP_STATUS_CHANGED_ID, false))) {
NS_WARNING("Failed to add bluetooth a2dp status changed observer!");
}
if (NS_FAILED(obs->AddObserver(this, BLUETOOTH_HFP_STATUS_CHANGED_ID, false))) {
NS_WARNING("Failed to add bluetooth hfp status changed observer!");
}
if (NS_FAILED(obs->AddObserver(this, BLUETOOTH_HFP_NREC_STATUS_CHANGED_ID, false))) {
NS_WARNING("Failed to add bluetooth hfp NREC status changed observer!");
}
if (NS_FAILED(obs->AddObserver(this, MOZ_SETTINGS_CHANGE_ID, false))) {
NS_WARNING("Failed to add mozsettings-changed observer!");
}
if (NS_FAILED(obs->AddObserver(this, AUDIO_CHANNEL_PROCESS_CHANGED, false))) {
NS_WARNING("Failed to add audio-channel-process-changed observer!");
}
#ifdef MOZ_B2G_RIL
char value[PROPERTY_VALUE_MAX];
property_get("ro.moz.mute.call.to_ril", value, "false");
if (!strcmp(value, "true")) {
mMuteCallToRIL = true;
}
#endif
}
AudioManager::~AudioManager() {
UnregisterSwitchObserver(SWITCH_HEADPHONES, mObserver);
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
NS_ENSURE_TRUE_VOID(obs);
if (NS_FAILED(obs->RemoveObserver(this, BLUETOOTH_SCO_STATUS_CHANGED_ID))) {
NS_WARNING("Failed to remove bluetooth sco status changed observer!");
}
if (NS_FAILED(obs->RemoveObserver(this, BLUETOOTH_A2DP_STATUS_CHANGED_ID))) {
NS_WARNING("Failed to remove bluetooth a2dp status changed observer!");
}
if (NS_FAILED(obs->RemoveObserver(this, BLUETOOTH_HFP_STATUS_CHANGED_ID))) {
NS_WARNING("Failed to remove bluetooth hfp status changed observer!");
}
if (NS_FAILED(obs->RemoveObserver(this, BLUETOOTH_HFP_NREC_STATUS_CHANGED_ID))) {
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.
if (!XRE_IsParentProcess()) {
MOZ_CRASH("Non-chrome processes should not get here.");
}
// Avoid createing multiple AudioManager instance inside main process.
if (!sAudioManager) {
sAudioManager = new AudioManager();
ClearOnShutdown(&sAudioManager);
}
nsRefPtr<AudioManager> audioMgr = sAudioManager.get();
return audioMgr.forget();
}
NS_IMETHODIMP
AudioManager::GetMicrophoneMuted(bool* aMicrophoneMuted)
{
#ifdef MOZ_B2G_RIL
if (mMuteCallToRIL) {
// Simply return cached mIsMicMuted if mute call go via RIL.
*aMicrophoneMuted = mIsMicMuted;
return NS_OK;
}
#endif
if (AudioSystem::isMicrophoneMuted(aMicrophoneMuted)) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
NS_IMETHODIMP
AudioManager::SetMicrophoneMuted(bool aMicrophoneMuted)
{
if (!AudioSystem::muteMicrophone(aMicrophoneMuted)) {
#ifdef MOZ_B2G_RIL
if (mMuteCallToRIL) {
// Extra mute request to RIL for specific platform.
nsCOMPtr<nsIRadioInterfaceLayer> ril = do_GetService("@mozilla.org/ril;1");
NS_ENSURE_TRUE(ril, NS_ERROR_FAILURE);
ril->SetMicrophoneMuted(aMicrophoneMuted);
mIsMicMuted = aMicrophoneMuted;
}
#endif
return NS_OK;
}
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
AudioManager::GetPhoneState(int32_t* aState)
{
*aState = mPhoneState;
return NS_OK;
}
NS_IMETHODIMP
AudioManager::SetPhoneState(int32_t aState)
{
if (mPhoneState == aState) {
return NS_OK;
}
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
if (obs) {
nsString state;
state.AppendInt(aState);
obs->NotifyObservers(nullptr, "phone-state-changed", state.get());
}
#if ANDROID_VERSION < 17
if (AudioSystem::setPhoneState(aState)) {
#else
if (AudioSystem::setPhoneState(static_cast<audio_mode_t>(aState))) {
#endif
return NS_ERROR_FAILURE;
}
mPhoneState = aState;
return NS_OK;
}
NS_IMETHODIMP
AudioManager::SetForceForUse(int32_t aUsage, int32_t aForce)
{
#if ANDROID_VERSION >= 15
status_t status = AudioSystem::setForceUse(
(audio_policy_force_use_t)aUsage,
(audio_policy_forced_cfg_t)aForce);
return status ? NS_ERROR_FAILURE : NS_OK;
#else
NS_NOTREACHED("Doesn't support force routing on GB version");
return NS_ERROR_UNEXPECTED;
#endif
}
NS_IMETHODIMP
AudioManager::GetForceForUse(int32_t aUsage, int32_t* aForce) {
#if ANDROID_VERSION >= 15
*aForce = AudioSystem::getForceUse((audio_policy_force_use_t)aUsage);
return NS_OK;
#else
NS_NOTREACHED("Doesn't support force routing on GB version");
return NS_ERROR_UNEXPECTED;
#endif
}
NS_IMETHODIMP
AudioManager::GetFmRadioAudioEnabled(bool *aFmRadioAudioEnabled)
{
*aFmRadioAudioEnabled = IsDeviceOn(AUDIO_DEVICE_OUT_FM);
return NS_OK;
}
NS_IMETHODIMP
AudioManager::SetFmRadioAudioEnabled(bool aFmRadioAudioEnabled)
{
AudioSystem::setDeviceConnectionState(AUDIO_DEVICE_OUT_FM,
aFmRadioAudioEnabled ? AUDIO_POLICY_DEVICE_STATE_AVAILABLE :
AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE, "");
UpdateHeadsetConnectionState(GetCurrentSwitchState(SWITCH_HEADPHONES));
// AUDIO_STREAM_FM is not used on recent gonk.
// AUDIO_STREAM_MUSIC is used for FM radio volume control.
#if ANDROID_VERSION < 19
// sync volume with music after powering on fm radio
if (aFmRadioAudioEnabled) {
uint32_t volIndex = mCurrentStreamVolumeTbl[AUDIO_STREAM_MUSIC];
SetStreamVolumeIndex(AUDIO_STREAM_FM, volIndex);
mCurrentStreamVolumeTbl[AUDIO_STREAM_FM] = volIndex;
}
#endif
return NS_OK;
}
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;
}
nsresult
AudioManager::SetVolumeByCategory(uint32_t aCategory, uint32_t aIndex)
{
nsresult status;
switch (static_cast<AudioVolumeCategories>(aCategory)) {
case VOLUME_MEDIA:
// AUDIO_STREAM_FM is not used on recent gonk.
// AUDIO_STREAM_MUSIC is used for FM radio volume control.
#if ANDROID_VERSION < 19
// sync FMRadio's volume with content channel.
if (IsDeviceOn(AUDIO_DEVICE_OUT_FM)) {
status = SetStreamVolumeIndex(AUDIO_STREAM_FM, aIndex);
if (NS_WARN_IF(NS_FAILED(status))) {
return status;
}
}
#endif
status = SetStreamVolumeIndex(AUDIO_STREAM_MUSIC, aIndex);
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 VOLUME_ALARM:
status = SetStreamVolumeIndex(AUDIO_STREAM_ALARM, aIndex);
break;
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<AudioVolumeCategories>(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<AudioVolumeCategories>(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<AudioChannel>(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(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<AudioChannel>(aChannel)) {
case AudioChannel::Normal:
case AudioChannel::Content:
*aIndex = GetVolumeByCategory(VOLUME_MEDIA);
break;
case AudioChannel::Notification:
case AudioChannel::Ringer:
case AudioChannel::Publicnotification:
case AudioChannel::System:
*aIndex = GetVolumeByCategory(VOLUME_NOTIFICATION);
break;
case AudioChannel::Alarm:
*aIndex = GetVolumeByCategory(VOLUME_ALARM);
break;
case AudioChannel::Telephony:
*aIndex = GetVolumeByCategory(category);
break;
default:
return NS_ERROR_INVALID_ARG;
}
return NS_OK;
}
NS_IMETHODIMP
AudioManager::GetMaxAudioChannelVolume(uint32_t aChannel, uint32_t* aMaxIndex)
{
if (!aMaxIndex) {
return NS_ERROR_NULL_POINTER;
}
AudioVolumeCategories category = (mPresentProfile == DEVICE_BLUETOOTH) ?
VOLUME_BLUETOOTH_SCO : VOLUME_TELEPHONY;
switch (static_cast<AudioChannel>(aChannel)) {
case AudioChannel::Normal:
case AudioChannel::Content:
*aMaxIndex = GetMaxVolumeByCategory(VOLUME_MEDIA);
break;
case AudioChannel::Notification:
case AudioChannel::Ringer:
case AudioChannel::Publicnotification:
case AudioChannel::System:
*aMaxIndex = GetMaxVolumeByCategory(VOLUME_NOTIFICATION);
break;
case AudioChannel::Alarm:
*aMaxIndex = GetMaxVolumeByCategory(VOLUME_ALARM);
break;
case AudioChannel::Telephony:
*aMaxIndex = GetMaxVolumeByCategory(category);
break;
default:
return NS_ERROR_INVALID_ARG;
}
return NS_OK;
}
nsresult
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
status = AudioSystem::setStreamVolumeIndex(
static_cast<audio_stream_type_t>(aStream),
aIndex);
return status ? NS_ERROR_FAILURE : NS_OK;
#else
#if ANDROID_VERSION < 19
if (aStream == AUDIO_STREAM_FM) {
status = AudioSystem::setStreamVolumeIndex(
static_cast<audio_stream_type_t>(aStream),
aIndex,
AUDIO_DEVICE_OUT_FM);
return status ? NS_ERROR_FAILURE : NS_OK;
}
#endif
if (mPresentProfile == DEVICE_PRIMARY) {
status = AudioSystem::setStreamVolumeIndex(
static_cast<audio_stream_type_t>(aStream),
aIndex,
AUDIO_DEVICE_OUT_SPEAKER);
status += AudioSystem::setStreamVolumeIndex(
static_cast<audio_stream_type_t>(aStream),
aIndex,
AUDIO_DEVICE_OUT_EARPIECE);
} else if (mPresentProfile == DEVICE_HEADSET) {
status = AudioSystem::setStreamVolumeIndex(
static_cast<audio_stream_type_t>(aStream),
aIndex,
AUDIO_DEVICE_OUT_WIRED_HEADSET);
status += AudioSystem::setStreamVolumeIndex(
static_cast<audio_stream_type_t>(aStream),
aIndex,
AUDIO_DEVICE_OUT_WIRED_HEADPHONE);
} else if (mPresentProfile == DEVICE_BLUETOOTH) {
status = AudioSystem::setStreamVolumeIndex(
static_cast<audio_stream_type_t>(aStream),
aIndex,
AUDIO_DEVICE_OUT_BLUETOOTH_A2DP);
status += AudioSystem::setStreamVolumeIndex(
static_cast<audio_stream_type_t>(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, uint32_t *aIndex) {
if (!aIndex) {
return NS_ERROR_INVALID_ARG;
}
if (aStream <= AUDIO_STREAM_DEFAULT || aStream >= AUDIO_STREAM_MAX) {
return NS_ERROR_INVALID_ARG;
}
*aIndex = mCurrentStreamVolumeTbl[aStream];
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;
}
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;
}
nsCOMPtr<nsISettingsServiceLock> 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<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::InitProfileVolume(AudioOutputProfiles aProfile,
uint32_t aCategory,
uint32_t 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 &&
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<AudioOutputProfiles>(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]);
}
}
} /* namespace gonk */
} /* namespace dom */
} /* namespace mozilla */