michael@0: /* Copyright 2012 Mozilla Foundation and Mozilla contributors michael@0: * michael@0: * Licensed under the Apache License, Version 2.0 (the "License"); michael@0: * you may not use this file except in compliance with the License. michael@0: * You may obtain a copy of the License at michael@0: * michael@0: * http://www.apache.org/licenses/LICENSE-2.0 michael@0: * michael@0: * Unless required by applicable law or agreed to in writing, software michael@0: * distributed under the License is distributed on an "AS IS" BASIS, michael@0: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. michael@0: * See the License for the specific language governing permissions and michael@0: * limitations under the License. michael@0: */ michael@0: michael@0: #include michael@0: #include michael@0: michael@0: #include "AudioChannelService.h" michael@0: #include "AudioManager.h" michael@0: michael@0: #include "nsIObserverService.h" michael@0: #ifdef MOZ_B2G_RIL michael@0: #include "nsIRadioInterfaceLayer.h" michael@0: #endif michael@0: #include "nsISettingsService.h" michael@0: #include "nsPrintfCString.h" michael@0: michael@0: #include "mozilla/Hal.h" michael@0: #include "mozilla/Services.h" michael@0: #include "base/message_loop.h" michael@0: michael@0: #include "BluetoothCommon.h" michael@0: #include "BluetoothHfpManagerBase.h" michael@0: michael@0: #include "nsJSUtils.h" michael@0: #include "nsCxPusher.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: michael@0: using namespace mozilla::dom::gonk; michael@0: using namespace android; michael@0: using namespace mozilla::hal; michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom::bluetooth; michael@0: michael@0: #define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "AudioManager" , ## args) michael@0: michael@0: #define HEADPHONES_STATUS_HEADSET MOZ_UTF16("headset") michael@0: #define HEADPHONES_STATUS_HEADPHONE MOZ_UTF16("headphone") michael@0: #define HEADPHONES_STATUS_OFF MOZ_UTF16("off") michael@0: #define HEADPHONES_STATUS_UNKNOWN MOZ_UTF16("unknown") michael@0: #define HEADPHONES_STATUS_CHANGED "headphones-status-changed" michael@0: #define MOZ_SETTINGS_CHANGE_ID "mozsettings-changed" michael@0: michael@0: static void BinderDeadCallback(status_t aErr); michael@0: static void InternalSetAudioRoutes(SwitchState aState); michael@0: // Refer AudioService.java from Android michael@0: static int sMaxStreamVolumeTbl[AUDIO_STREAM_CNT] = { michael@0: 5, // voice call michael@0: 15, // system michael@0: 15, // ring michael@0: 15, // music michael@0: 15, // alarm michael@0: 15, // notification michael@0: 15, // BT SCO michael@0: 15, // enforced audible michael@0: 15, // DTMF michael@0: 15, // TTS michael@0: 15, // FM michael@0: }; michael@0: // A bitwise variable for recording what kind of headset is attached. michael@0: static int sHeadsetState; michael@0: static const int kBtSampleRate = 8000; michael@0: static bool sSwitchDone = true; michael@0: michael@0: namespace mozilla { michael@0: namespace dom { michael@0: namespace gonk { michael@0: class RecoverTask : public nsRunnable michael@0: { michael@0: public: michael@0: RecoverTask() {} michael@0: NS_IMETHODIMP Run() { michael@0: nsCOMPtr amService = do_GetService(NS_AUDIOMANAGER_CONTRACTID); michael@0: NS_ENSURE_TRUE(amService, NS_OK); michael@0: AudioManager *am = static_cast(amService.get()); michael@0: for (int loop = 0; loop < AUDIO_STREAM_CNT; loop++) { michael@0: AudioSystem::initStreamVolume(static_cast(loop), 0, michael@0: sMaxStreamVolumeTbl[loop]); michael@0: int32_t index; michael@0: am->GetStreamVolumeIndex(loop, &index); michael@0: am->SetStreamVolumeIndex(loop, index); michael@0: } michael@0: michael@0: if (sHeadsetState & AUDIO_DEVICE_OUT_WIRED_HEADSET) michael@0: InternalSetAudioRoutes(SWITCH_STATE_HEADSET); michael@0: else if (sHeadsetState & AUDIO_DEVICE_OUT_WIRED_HEADPHONE) michael@0: InternalSetAudioRoutes(SWITCH_STATE_HEADPHONE); michael@0: else michael@0: InternalSetAudioRoutes(SWITCH_STATE_OFF); michael@0: michael@0: int32_t phoneState = nsIAudioManager::PHONE_STATE_INVALID; michael@0: am->GetPhoneState(&phoneState); michael@0: #if ANDROID_VERSION < 17 michael@0: AudioSystem::setPhoneState(phoneState); michael@0: #else michael@0: AudioSystem::setPhoneState(static_cast(phoneState)); michael@0: #endif michael@0: michael@0: AudioSystem::get_audio_flinger(); michael@0: return NS_OK; michael@0: } michael@0: }; michael@0: michael@0: class AudioChannelVolInitCallback MOZ_FINAL : public nsISettingsServiceCallback michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: AudioChannelVolInitCallback() {} michael@0: michael@0: NS_IMETHOD Handle(const nsAString& aName, JS::Handle aResult) michael@0: { michael@0: nsCOMPtr audioManager = michael@0: do_GetService(NS_AUDIOMANAGER_CONTRACTID); michael@0: NS_ENSURE_TRUE(JSVAL_IS_INT(aResult), NS_OK); michael@0: michael@0: int32_t volIndex = JSVAL_TO_INT(aResult); michael@0: if (aName.EqualsLiteral("audio.volume.content")) { michael@0: audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Content, michael@0: volIndex); michael@0: } else if (aName.EqualsLiteral("audio.volume.notification")) { michael@0: audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Notification, michael@0: volIndex); michael@0: } else if (aName.EqualsLiteral("audio.volume.alarm")) { michael@0: audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Alarm, michael@0: volIndex); michael@0: } else if (aName.EqualsLiteral("audio.volume.telephony")) { michael@0: audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Telephony, michael@0: volIndex); michael@0: } else if (aName.EqualsLiteral("audio.volume.bt_sco")) { michael@0: static_cast(audioManager.get())->SetStreamVolumeIndex( michael@0: AUDIO_STREAM_BLUETOOTH_SCO, volIndex); michael@0: } else { michael@0: MOZ_ASSUME_UNREACHABLE("unexpected audio channel for initializing " michael@0: "volume control"); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHOD HandleError(const nsAString& aName) michael@0: { michael@0: LOG("AudioChannelVolInitCallback::HandleError: %s\n", michael@0: NS_ConvertUTF16toUTF8(aName).get()); michael@0: return NS_OK; michael@0: } michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(AudioChannelVolInitCallback, nsISettingsServiceCallback) michael@0: } /* namespace gonk */ michael@0: } /* namespace dom */ michael@0: } /* namespace mozilla */ michael@0: michael@0: static void michael@0: BinderDeadCallback(status_t aErr) michael@0: { michael@0: if (aErr == DEAD_OBJECT) { michael@0: NS_DispatchToMainThread(new RecoverTask()); michael@0: } michael@0: } michael@0: michael@0: static bool michael@0: IsDeviceOn(audio_devices_t device) michael@0: { michael@0: if (static_cast< michael@0: audio_policy_dev_state_t (*) (audio_devices_t, const char *) michael@0: >(AudioSystem::getDeviceConnectionState)) michael@0: return AudioSystem::getDeviceConnectionState(device, "") == michael@0: AUDIO_POLICY_DEVICE_STATE_AVAILABLE; michael@0: michael@0: return false; michael@0: } michael@0: michael@0: static void ProcessDelayedAudioRoute(SwitchState aState) michael@0: { michael@0: if (sSwitchDone) michael@0: return; michael@0: InternalSetAudioRoutes(aState); michael@0: sSwitchDone = true; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(AudioManager, nsIAudioManager, nsIObserver) michael@0: michael@0: static void michael@0: InternalSetAudioRoutesICS(SwitchState aState) michael@0: { michael@0: if (aState == SWITCH_STATE_HEADSET) { michael@0: AudioSystem::setDeviceConnectionState(AUDIO_DEVICE_OUT_WIRED_HEADSET, michael@0: AUDIO_POLICY_DEVICE_STATE_AVAILABLE, ""); michael@0: sHeadsetState |= AUDIO_DEVICE_OUT_WIRED_HEADSET; michael@0: } else if (aState == SWITCH_STATE_HEADPHONE) { michael@0: AudioSystem::setDeviceConnectionState(AUDIO_DEVICE_OUT_WIRED_HEADPHONE, michael@0: AUDIO_POLICY_DEVICE_STATE_AVAILABLE, ""); michael@0: sHeadsetState |= AUDIO_DEVICE_OUT_WIRED_HEADPHONE; michael@0: } else if (aState == SWITCH_STATE_OFF) { michael@0: AudioSystem::setDeviceConnectionState(static_cast(sHeadsetState), michael@0: AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE, ""); michael@0: sHeadsetState = 0; michael@0: } michael@0: } michael@0: michael@0: static void michael@0: InternalSetAudioRoutes(SwitchState aState) michael@0: { michael@0: if (static_cast< michael@0: status_t (*)(audio_devices_t, audio_policy_dev_state_t, const char*) michael@0: >(AudioSystem::setDeviceConnectionState)) { michael@0: InternalSetAudioRoutesICS(aState); michael@0: } else { michael@0: NS_NOTREACHED("Doesn't support audio routing on GB version"); michael@0: } michael@0: } michael@0: michael@0: void michael@0: AudioManager::HandleBluetoothStatusChanged(nsISupports* aSubject, michael@0: const char* aTopic, michael@0: const nsCString aAddress) michael@0: { michael@0: #ifdef MOZ_B2G_BT michael@0: bool status; michael@0: if (!strcmp(aTopic, BLUETOOTH_SCO_STATUS_CHANGED_ID)) { michael@0: BluetoothHfpManagerBase* hfp = michael@0: static_cast(aSubject); michael@0: status = hfp->IsScoConnected(); michael@0: } else { michael@0: BluetoothProfileManagerBase* profile = michael@0: static_cast(aSubject); michael@0: status = profile->IsConnected(); michael@0: } michael@0: michael@0: audio_policy_dev_state_t audioState = status ? michael@0: AUDIO_POLICY_DEVICE_STATE_AVAILABLE : michael@0: AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE; michael@0: michael@0: if (!strcmp(aTopic, BLUETOOTH_SCO_STATUS_CHANGED_ID)) { michael@0: if (audioState == AUDIO_POLICY_DEVICE_STATE_AVAILABLE) { michael@0: String8 cmd; michael@0: cmd.appendFormat("bt_samplerate=%d", kBtSampleRate); michael@0: AudioSystem::setParameters(0, cmd); michael@0: SetForceForUse(nsIAudioManager::USE_COMMUNICATION, nsIAudioManager::FORCE_BT_SCO); michael@0: } else { michael@0: int32_t force; michael@0: GetForceForUse(nsIAudioManager::USE_COMMUNICATION, &force); michael@0: if (force == nsIAudioManager::FORCE_BT_SCO) michael@0: SetForceForUse(nsIAudioManager::USE_COMMUNICATION, nsIAudioManager::FORCE_NONE); michael@0: } michael@0: } else if (!strcmp(aTopic, BLUETOOTH_A2DP_STATUS_CHANGED_ID)) { michael@0: AudioSystem::setDeviceConnectionState(AUDIO_DEVICE_OUT_BLUETOOTH_A2DP, michael@0: audioState, aAddress.get()); michael@0: if (audioState == AUDIO_POLICY_DEVICE_STATE_AVAILABLE) { michael@0: String8 cmd("bluetooth_enabled=true"); michael@0: AudioSystem::setParameters(0, cmd); michael@0: cmd.setTo("A2dpSuspended=false"); michael@0: AudioSystem::setParameters(0, cmd); michael@0: } else { michael@0: String8 cmd("bluetooth_enabled=false"); michael@0: AudioSystem::setParameters(0, cmd); michael@0: cmd.setTo("A2dpSuspended=true"); michael@0: AudioSystem::setParameters(0, cmd); michael@0: } michael@0: } else if (!strcmp(aTopic, BLUETOOTH_HFP_STATUS_CHANGED_ID)) { michael@0: AudioSystem::setDeviceConnectionState(AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET, michael@0: audioState, aAddress.get()); michael@0: AudioSystem::setDeviceConnectionState(AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET, michael@0: audioState, aAddress.get()); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: nsresult michael@0: AudioManager::Observe(nsISupports* aSubject, michael@0: const char* aTopic, michael@0: const char16_t* aData) michael@0: { michael@0: if ((strcmp(aTopic, BLUETOOTH_SCO_STATUS_CHANGED_ID) == 0) || michael@0: (strcmp(aTopic, BLUETOOTH_HFP_STATUS_CHANGED_ID) == 0) || michael@0: (strcmp(aTopic, BLUETOOTH_A2DP_STATUS_CHANGED_ID) == 0)) { michael@0: nsCString address = NS_ConvertUTF16toUTF8(nsDependentString(aData)); michael@0: if (address.IsEmpty()) { michael@0: NS_WARNING(nsPrintfCString("Invalid address of %s", aTopic).get()); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: HandleBluetoothStatusChanged(aSubject, aTopic, address); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // To process the volume control on each audio channel according to michael@0: // change of settings michael@0: else if (!strcmp(aTopic, MOZ_SETTINGS_CHANGE_ID)) { michael@0: AutoSafeJSContext cx; michael@0: nsDependentString dataStr(aData); michael@0: JS::Rooted val(cx); michael@0: if (!JS_ParseJSON(cx, dataStr.get(), dataStr.Length(), &val) || michael@0: !val.isObject()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: JS::Rooted obj(cx, &val.toObject()); michael@0: JS::Rooted key(cx); michael@0: if (!JS_GetProperty(cx, obj, "key", &key) || michael@0: !key.isString()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: JS::Rooted jsKey(cx, JS::ToString(cx, key)); michael@0: if (!jsKey) { michael@0: return NS_OK; michael@0: } michael@0: nsDependentJSString keyStr; michael@0: if (!keyStr.init(cx, jsKey) || !keyStr.EqualsLiteral("audio.volume.bt_sco")) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: JS::Rooted value(cx); michael@0: if (!JS_GetProperty(cx, obj, "value", &value) || !value.isInt32()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: int32_t index = value.toInt32(); michael@0: SetStreamVolumeIndex(AUDIO_STREAM_BLUETOOTH_SCO, index); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_WARNING("Unexpected topic in AudioManager"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: static void michael@0: NotifyHeadphonesStatus(SwitchState aState) michael@0: { michael@0: nsCOMPtr obs = mozilla::services::GetObserverService(); michael@0: if (obs) { michael@0: if (aState == SWITCH_STATE_HEADSET) { michael@0: obs->NotifyObservers(nullptr, HEADPHONES_STATUS_CHANGED, HEADPHONES_STATUS_HEADSET); michael@0: } else if (aState == SWITCH_STATE_HEADPHONE) { michael@0: obs->NotifyObservers(nullptr, HEADPHONES_STATUS_CHANGED, HEADPHONES_STATUS_HEADPHONE); michael@0: } else if (aState == SWITCH_STATE_OFF) { michael@0: obs->NotifyObservers(nullptr, HEADPHONES_STATUS_CHANGED, HEADPHONES_STATUS_OFF); michael@0: } else { michael@0: obs->NotifyObservers(nullptr, HEADPHONES_STATUS_CHANGED, HEADPHONES_STATUS_UNKNOWN); michael@0: } michael@0: } michael@0: } michael@0: michael@0: class HeadphoneSwitchObserver : public SwitchObserver michael@0: { michael@0: public: michael@0: void Notify(const SwitchEvent& aEvent) { michael@0: NotifyHeadphonesStatus(aEvent.status()); michael@0: // When user pulled out the headset, a delay of routing here can avoid the leakage of audio from speaker. michael@0: if (aEvent.status() == SWITCH_STATE_OFF && sSwitchDone) { michael@0: MessageLoop::current()->PostDelayedTask( michael@0: FROM_HERE, NewRunnableFunction(&ProcessDelayedAudioRoute, SWITCH_STATE_OFF), 1000); michael@0: sSwitchDone = false; michael@0: } else if (aEvent.status() != SWITCH_STATE_OFF) { michael@0: InternalSetAudioRoutes(aEvent.status()); michael@0: sSwitchDone = true; michael@0: } michael@0: } michael@0: }; michael@0: michael@0: AudioManager::AudioManager() michael@0: : mPhoneState(PHONE_STATE_CURRENT) michael@0: , mObserver(new HeadphoneSwitchObserver()) michael@0: #ifdef MOZ_B2G_RIL michael@0: , mMuteCallToRIL(false) michael@0: #endif michael@0: { michael@0: RegisterSwitchObserver(SWITCH_HEADPHONES, mObserver); michael@0: michael@0: InternalSetAudioRoutes(GetCurrentSwitchState(SWITCH_HEADPHONES)); michael@0: NotifyHeadphonesStatus(GetCurrentSwitchState(SWITCH_HEADPHONES)); michael@0: michael@0: for (int loop = 0; loop < AUDIO_STREAM_CNT; loop++) { michael@0: AudioSystem::initStreamVolume(static_cast(loop), 0, michael@0: sMaxStreamVolumeTbl[loop]); michael@0: mCurrentStreamVolumeTbl[loop] = sMaxStreamVolumeTbl[loop]; michael@0: } michael@0: // Force publicnotification to output at maximal volume michael@0: SetStreamVolumeIndex(AUDIO_STREAM_ENFORCED_AUDIBLE, michael@0: sMaxStreamVolumeTbl[AUDIO_STREAM_ENFORCED_AUDIBLE]); michael@0: michael@0: // Get the initial volume index from settings DB during boot up. michael@0: nsCOMPtr settingsService = michael@0: do_GetService("@mozilla.org/settingsService;1"); michael@0: NS_ENSURE_TRUE_VOID(settingsService); michael@0: nsCOMPtr lock; michael@0: nsresult rv = settingsService->CreateLock(nullptr, getter_AddRefs(lock)); michael@0: NS_ENSURE_SUCCESS_VOID(rv); michael@0: nsCOMPtr callback = new AudioChannelVolInitCallback(); michael@0: NS_ENSURE_TRUE_VOID(callback); michael@0: lock->Get("audio.volume.content", callback); michael@0: lock->Get("audio.volume.notification", callback); michael@0: lock->Get("audio.volume.alarm", callback); michael@0: lock->Get("audio.volume.telephony", callback); michael@0: lock->Get("audio.volume.bt_sco", callback); michael@0: michael@0: // Gecko only control stream volume not master so set to default value michael@0: // directly. michael@0: AudioSystem::setMasterVolume(1.0); michael@0: AudioSystem::setErrorCallback(BinderDeadCallback); michael@0: michael@0: nsCOMPtr obs = services::GetObserverService(); michael@0: NS_ENSURE_TRUE_VOID(obs); michael@0: if (NS_FAILED(obs->AddObserver(this, BLUETOOTH_SCO_STATUS_CHANGED_ID, false))) { michael@0: NS_WARNING("Failed to add bluetooth sco status changed observer!"); michael@0: } michael@0: if (NS_FAILED(obs->AddObserver(this, BLUETOOTH_A2DP_STATUS_CHANGED_ID, false))) { michael@0: NS_WARNING("Failed to add bluetooth a2dp status changed observer!"); michael@0: } michael@0: if (NS_FAILED(obs->AddObserver(this, BLUETOOTH_HFP_STATUS_CHANGED_ID, false))) { michael@0: NS_WARNING("Failed to add bluetooth hfp status changed observer!"); michael@0: } michael@0: if (NS_FAILED(obs->AddObserver(this, MOZ_SETTINGS_CHANGE_ID, false))) { michael@0: NS_WARNING("Failed to add mozsettings-changed observer!"); michael@0: } michael@0: michael@0: #ifdef MOZ_B2G_RIL michael@0: char value[PROPERTY_VALUE_MAX]; michael@0: property_get("ro.moz.mute.call.to_ril", value, "false"); michael@0: if (!strcmp(value, "true")) { michael@0: mMuteCallToRIL = true; michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: AudioManager::~AudioManager() { michael@0: UnregisterSwitchObserver(SWITCH_HEADPHONES, mObserver); michael@0: michael@0: nsCOMPtr obs = services::GetObserverService(); michael@0: NS_ENSURE_TRUE_VOID(obs); michael@0: if (NS_FAILED(obs->RemoveObserver(this, BLUETOOTH_SCO_STATUS_CHANGED_ID))) { michael@0: NS_WARNING("Failed to remove bluetooth sco status changed observer!"); michael@0: } michael@0: if (NS_FAILED(obs->RemoveObserver(this, BLUETOOTH_A2DP_STATUS_CHANGED_ID))) { michael@0: NS_WARNING("Failed to remove bluetooth a2dp status changed observer!"); michael@0: } michael@0: if (NS_FAILED(obs->RemoveObserver(this, BLUETOOTH_HFP_STATUS_CHANGED_ID))) { michael@0: NS_WARNING("Failed to remove bluetooth hfp status changed observer!"); michael@0: } michael@0: if (NS_FAILED(obs->RemoveObserver(this, MOZ_SETTINGS_CHANGE_ID))) { michael@0: NS_WARNING("Failed to remove mozsettings-changed observer!"); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: AudioManager::GetMicrophoneMuted(bool* aMicrophoneMuted) michael@0: { michael@0: #ifdef MOZ_B2G_RIL michael@0: if (mMuteCallToRIL) { michael@0: // Simply return cached mIsMicMuted if mute call go via RIL. michael@0: *aMicrophoneMuted = mIsMicMuted; michael@0: return NS_OK; michael@0: } michael@0: #endif michael@0: michael@0: if (AudioSystem::isMicrophoneMuted(aMicrophoneMuted)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: AudioManager::SetMicrophoneMuted(bool aMicrophoneMuted) michael@0: { michael@0: if (!AudioSystem::muteMicrophone(aMicrophoneMuted)) { michael@0: #ifdef MOZ_B2G_RIL michael@0: if (mMuteCallToRIL) { michael@0: // Extra mute request to RIL for specific platform. michael@0: nsCOMPtr ril = do_GetService("@mozilla.org/ril;1"); michael@0: NS_ENSURE_TRUE(ril, NS_ERROR_FAILURE); michael@0: ril->SetMicrophoneMuted(aMicrophoneMuted); michael@0: mIsMicMuted = aMicrophoneMuted; michael@0: } michael@0: #endif michael@0: return NS_OK; michael@0: } michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: AudioManager::GetPhoneState(int32_t* aState) michael@0: { michael@0: *aState = mPhoneState; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: AudioManager::SetPhoneState(int32_t aState) michael@0: { michael@0: if (mPhoneState == aState) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr obs = services::GetObserverService(); michael@0: if (obs) { michael@0: nsString state; michael@0: state.AppendInt(aState); michael@0: obs->NotifyObservers(nullptr, "phone-state-changed", state.get()); michael@0: } michael@0: michael@0: #if ANDROID_VERSION < 17 michael@0: if (AudioSystem::setPhoneState(aState)) { michael@0: #else michael@0: if (AudioSystem::setPhoneState(static_cast(aState))) { michael@0: #endif michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: mPhoneState = aState; michael@0: michael@0: if (mPhoneAudioAgent) { michael@0: mPhoneAudioAgent->StopPlaying(); michael@0: mPhoneAudioAgent = nullptr; michael@0: } michael@0: michael@0: if (aState == PHONE_STATE_IN_CALL || aState == PHONE_STATE_RINGTONE) { michael@0: mPhoneAudioAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1"); michael@0: MOZ_ASSERT(mPhoneAudioAgent); michael@0: if (aState == PHONE_STATE_IN_CALL) { michael@0: // Telephony doesn't be paused by any other channels. michael@0: mPhoneAudioAgent->Init(nullptr, (int32_t)AudioChannel::Telephony, nullptr); michael@0: } else { michael@0: mPhoneAudioAgent->Init(nullptr, (int32_t)AudioChannel::Ringer, nullptr); michael@0: } michael@0: michael@0: // Telephony can always play. michael@0: int32_t canPlay; michael@0: mPhoneAudioAgent->StartPlaying(&canPlay); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: AudioManager::SetForceForUse(int32_t aUsage, int32_t aForce) michael@0: { michael@0: if (static_cast< michael@0: status_t (*)(audio_policy_force_use_t, audio_policy_forced_cfg_t) michael@0: >(AudioSystem::setForceUse)) { michael@0: // Dynamically resolved the ICS signature. michael@0: status_t status = AudioSystem::setForceUse( michael@0: (audio_policy_force_use_t)aUsage, michael@0: (audio_policy_forced_cfg_t)aForce); michael@0: return status ? NS_ERROR_FAILURE : NS_OK; michael@0: } michael@0: michael@0: NS_NOTREACHED("Doesn't support force routing on GB version"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: AudioManager::GetForceForUse(int32_t aUsage, int32_t* aForce) { michael@0: if (static_cast< michael@0: audio_policy_forced_cfg_t (*)(audio_policy_force_use_t) michael@0: >(AudioSystem::getForceUse)) { michael@0: // Dynamically resolved the ICS signature. michael@0: *aForce = AudioSystem::getForceUse((audio_policy_force_use_t)aUsage); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_NOTREACHED("Doesn't support force routing on GB version"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: AudioManager::GetFmRadioAudioEnabled(bool *aFmRadioAudioEnabled) michael@0: { michael@0: *aFmRadioAudioEnabled = IsDeviceOn(AUDIO_DEVICE_OUT_FM); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: AudioManager::SetFmRadioAudioEnabled(bool aFmRadioAudioEnabled) michael@0: { michael@0: if (static_cast< michael@0: status_t (*) (AudioSystem::audio_devices, AudioSystem::device_connection_state, const char *) michael@0: >(AudioSystem::setDeviceConnectionState)) { michael@0: AudioSystem::setDeviceConnectionState(AUDIO_DEVICE_OUT_FM, michael@0: aFmRadioAudioEnabled ? AUDIO_POLICY_DEVICE_STATE_AVAILABLE : michael@0: AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE, ""); michael@0: InternalSetAudioRoutes(GetCurrentSwitchState(SWITCH_HEADPHONES)); michael@0: // sync volume with music after powering on fm radio michael@0: if (aFmRadioAudioEnabled) { michael@0: int32_t volIndex = mCurrentStreamVolumeTbl[AUDIO_STREAM_MUSIC]; michael@0: SetStreamVolumeIndex(AUDIO_STREAM_FM, volIndex); michael@0: mCurrentStreamVolumeTbl[AUDIO_STREAM_FM] = volIndex; michael@0: } michael@0: return NS_OK; michael@0: } else { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: AudioManager::SetAudioChannelVolume(int32_t aChannel, int32_t aIndex) { michael@0: nsresult status; michael@0: michael@0: switch (static_cast(aChannel)) { michael@0: case AudioChannel::Content: michael@0: // sync FMRadio's volume with content channel. michael@0: if (IsDeviceOn(AUDIO_DEVICE_OUT_FM)) { michael@0: status = SetStreamVolumeIndex(AUDIO_STREAM_FM, aIndex); michael@0: NS_ENSURE_SUCCESS(status, status); michael@0: } michael@0: status = SetStreamVolumeIndex(AUDIO_STREAM_MUSIC, aIndex); michael@0: NS_ENSURE_SUCCESS(status, status); michael@0: status = SetStreamVolumeIndex(AUDIO_STREAM_SYSTEM, aIndex); michael@0: break; michael@0: case AudioChannel::Notification: michael@0: status = SetStreamVolumeIndex(AUDIO_STREAM_NOTIFICATION, aIndex); michael@0: NS_ENSURE_SUCCESS(status, status); michael@0: status = SetStreamVolumeIndex(AUDIO_STREAM_RING, aIndex); michael@0: break; michael@0: case AudioChannel::Alarm: michael@0: status = SetStreamVolumeIndex(AUDIO_STREAM_ALARM, aIndex); michael@0: break; michael@0: case AudioChannel::Telephony: michael@0: status = SetStreamVolumeIndex(AUDIO_STREAM_VOICE_CALL, aIndex); michael@0: break; michael@0: default: michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: return status; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: AudioManager::GetAudioChannelVolume(int32_t aChannel, int32_t* aIndex) { michael@0: if (!aIndex) { michael@0: return NS_ERROR_NULL_POINTER; michael@0: } michael@0: michael@0: switch (static_cast(aChannel)) { michael@0: case AudioChannel::Content: michael@0: MOZ_ASSERT(mCurrentStreamVolumeTbl[AUDIO_STREAM_MUSIC] == michael@0: mCurrentStreamVolumeTbl[AUDIO_STREAM_SYSTEM]); michael@0: *aIndex = mCurrentStreamVolumeTbl[AUDIO_STREAM_MUSIC]; michael@0: break; michael@0: case AudioChannel::Notification: michael@0: MOZ_ASSERT(mCurrentStreamVolumeTbl[AUDIO_STREAM_NOTIFICATION] == michael@0: mCurrentStreamVolumeTbl[AUDIO_STREAM_RING]); michael@0: *aIndex = mCurrentStreamVolumeTbl[AUDIO_STREAM_NOTIFICATION]; michael@0: break; michael@0: case AudioChannel::Alarm: michael@0: *aIndex = mCurrentStreamVolumeTbl[AUDIO_STREAM_ALARM]; michael@0: break; michael@0: case AudioChannel::Telephony: michael@0: *aIndex = mCurrentStreamVolumeTbl[AUDIO_STREAM_VOICE_CALL]; michael@0: break; michael@0: default: michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: AudioManager::GetMaxAudioChannelVolume(int32_t aChannel, int32_t* aMaxIndex) { michael@0: if (!aMaxIndex) { michael@0: return NS_ERROR_NULL_POINTER; michael@0: } michael@0: michael@0: int32_t stream; michael@0: switch (static_cast(aChannel)) { michael@0: case AudioChannel::Content: michael@0: MOZ_ASSERT(sMaxStreamVolumeTbl[AUDIO_STREAM_MUSIC] == michael@0: sMaxStreamVolumeTbl[AUDIO_STREAM_SYSTEM]); michael@0: stream = AUDIO_STREAM_MUSIC; michael@0: break; michael@0: case AudioChannel::Notification: michael@0: MOZ_ASSERT(sMaxStreamVolumeTbl[AUDIO_STREAM_NOTIFICATION] == michael@0: sMaxStreamVolumeTbl[AUDIO_STREAM_RING]); michael@0: stream = AUDIO_STREAM_NOTIFICATION; michael@0: break; michael@0: case AudioChannel::Alarm: michael@0: stream = AUDIO_STREAM_ALARM; michael@0: break; michael@0: case AudioChannel::Telephony: michael@0: stream = AUDIO_STREAM_VOICE_CALL; michael@0: break; michael@0: default: michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: *aMaxIndex = sMaxStreamVolumeTbl[stream]; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: AudioManager::SetStreamVolumeIndex(int32_t aStream, int32_t aIndex) { michael@0: if (aIndex < 0 || aIndex > sMaxStreamVolumeTbl[aStream]) { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: mCurrentStreamVolumeTbl[aStream] = aIndex; michael@0: status_t status; michael@0: #if ANDROID_VERSION < 17 michael@0: status = AudioSystem::setStreamVolumeIndex( michael@0: static_cast(aStream), michael@0: aIndex); michael@0: return status ? NS_ERROR_FAILURE : NS_OK; michael@0: #else michael@0: int device = 0; michael@0: michael@0: if (aStream == AUDIO_STREAM_BLUETOOTH_SCO) { michael@0: device = AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET; michael@0: } else if (aStream == AUDIO_STREAM_FM) { michael@0: device = AUDIO_DEVICE_OUT_FM; michael@0: } michael@0: michael@0: if (device != 0) { michael@0: status = AudioSystem::setStreamVolumeIndex( michael@0: static_cast(aStream), michael@0: aIndex, michael@0: device); michael@0: return status ? NS_ERROR_FAILURE : NS_OK; michael@0: } michael@0: michael@0: status = AudioSystem::setStreamVolumeIndex( michael@0: static_cast(aStream), michael@0: aIndex, michael@0: AUDIO_DEVICE_OUT_BLUETOOTH_A2DP); michael@0: status += AudioSystem::setStreamVolumeIndex( michael@0: static_cast(aStream), michael@0: aIndex, michael@0: AUDIO_DEVICE_OUT_SPEAKER); michael@0: status += AudioSystem::setStreamVolumeIndex( michael@0: static_cast(aStream), michael@0: aIndex, michael@0: AUDIO_DEVICE_OUT_WIRED_HEADSET); michael@0: status += AudioSystem::setStreamVolumeIndex( michael@0: static_cast(aStream), michael@0: aIndex, michael@0: AUDIO_DEVICE_OUT_WIRED_HEADPHONE); michael@0: status += AudioSystem::setStreamVolumeIndex( michael@0: static_cast(aStream), michael@0: aIndex, michael@0: AUDIO_DEVICE_OUT_EARPIECE); michael@0: return status ? NS_ERROR_FAILURE : NS_OK; michael@0: #endif michael@0: } michael@0: michael@0: nsresult michael@0: AudioManager::GetStreamVolumeIndex(int32_t aStream, int32_t *aIndex) { michael@0: if (!aIndex) { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: if (aStream <= AUDIO_STREAM_DEFAULT || aStream >= AUDIO_STREAM_MAX) { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: *aIndex = mCurrentStreamVolumeTbl[aStream]; michael@0: michael@0: return NS_OK; michael@0: }