diff --git a/dom/telephony/Telephony.cpp b/dom/telephony/Telephony.cpp index 4ca27cab41d7..75734c97e93c 100644 --- a/dom/telephony/Telephony.cpp +++ b/dom/telephony/Telephony.cpp @@ -7,6 +7,7 @@ #include "Telephony.h" #include "mozilla/Preferences.h" +#include "mozilla/dom/AudioChannelBinding.h" #include "mozilla/dom/CallEvent.h" #include "mozilla/dom/MozMobileConnectionBinding.h" #include "mozilla/dom/Promise.h" @@ -20,6 +21,7 @@ #include "nsServiceManagerUtils.h" #include "nsThreadUtils.h" +#include "AudioChannelAgent.h" #include "CallsList.h" #include "TelephonyCall.h" #include "TelephonyCallGroup.h" @@ -62,8 +64,11 @@ public: }; Telephony::Telephony(nsPIDOMWindow* aOwner) - : DOMEventTargetHelper(aOwner) + : DOMEventTargetHelper(aOwner), + mIsAudioStartPlaying(false), + mAudioAgentNotify(nsIAudioChannelAgent::AUDIO_AGENT_NOTIFY) { + MOZ_ASSERT(aOwner); nsCOMPtr global = do_QueryInterface(aOwner); MOZ_ASSERT(global); @@ -518,6 +523,61 @@ Telephony::StopTone(const Optional& aServiceId, ErrorResult& aRv) aRv = mService->StopTone(serviceId); } +void +Telephony::OwnAudioChannel(ErrorResult& aRv) +{ + if (mAudioAgent) { + return; + } + + mAudioAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1"); + MOZ_ASSERT(mAudioAgent); + aRv = mAudioAgent->Init(GetParentObject(), + (int32_t)AudioChannel::Telephony, this); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + aRv = HandleAudioAgentState(); + if (NS_WARN_IF(aRv.Failed())) { + return; + } +} + +nsresult +Telephony::HandleAudioAgentState() +{ + if (!mAudioAgent) { + return NS_OK; + } + + Nullable activeCall; + GetActive(activeCall); + nsresult rv; + // Only stop agent when the call is disconnected. + if ((!mCalls.Length() && !mGroup->CallsArray().Length()) && + mIsAudioStartPlaying) { + mIsAudioStartPlaying = false; + rv = mAudioAgent->NotifyStoppedPlaying(mAudioAgentNotify); + mAudioAgent = nullptr; + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } else if (!activeCall.IsNull() && !mIsAudioStartPlaying) { + mIsAudioStartPlaying = true; + float volume = 1.0; + bool muted = false; + rv = mAudioAgent->NotifyStartedPlaying(mAudioAgentNotify, &volume, &muted); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + rv = WindowVolumeChanged(volume, muted); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + return NS_OK; +} + bool Telephony::GetMuted(ErrorResult& aRv) const { @@ -591,13 +651,62 @@ Telephony::GetReady(ErrorResult& aRv) const return promise.forget(); } +// nsIAudioChannelAgentCallback + +NS_IMETHODIMP +Telephony::WindowVolumeChanged(float aVolume, bool aMuted) +{ + // Check the limitation of the network connection + if (mCalls.Length() > 1 || + (mCalls.Length() == 1 && mGroup->CallsArray().Length())) { + return NS_ERROR_FAILURE; + } + + ErrorResult rv; + nsCOMPtr global = do_QueryInterface(GetOwner()); + nsRefPtr promise = Promise::Create(global, rv); + if (NS_WARN_IF(rv.Failed())) { + return rv.StealNSResult(); + } + + // Check the single call or conference call + bool isSingleCall = mCalls.Length(); + nsCOMPtr callback = new TelephonyCallback(promise); + if (isSingleCall) { + rv = aMuted ? mCalls[0]->Hold(callback) : mCalls[0]->Resume(callback); + } else { + rv = aMuted ? mGroup->Hold(callback) : mGroup->Resume(callback); + } + if (NS_WARN_IF(rv.Failed())) { + return rv.StealNSResult(); + } + + return NS_OK; +} + +NS_IMETHODIMP +Telephony::WindowAudioCaptureChanged() +{ + // Do nothing + return NS_OK; +} + // nsITelephonyListener NS_IMETHODIMP Telephony::CallStateChanged(uint32_t aLength, nsITelephonyCallInfo** aAllInfo) { + nsresult rv; for (uint32_t i = 0; i < aLength; ++i) { - HandleCallInfo(aAllInfo[i]); + rv = HandleCallInfo(aAllInfo[i]); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + rv = HandleAudioAgentState(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; } return NS_OK; } @@ -605,7 +714,8 @@ Telephony::CallStateChanged(uint32_t aLength, nsITelephonyCallInfo** aAllInfo) NS_IMETHODIMP Telephony::EnumerateCallState(nsITelephonyCallInfo* aInfo) { - return HandleCallInfo(aInfo); + uint32_t currentCallNum = 1; + return CallStateChanged(currentCallNum, &aInfo); } NS_IMETHODIMP diff --git a/dom/telephony/Telephony.h b/dom/telephony/Telephony.h index 0ef8ce27704d..77863fec624a 100644 --- a/dom/telephony/Telephony.h +++ b/dom/telephony/Telephony.h @@ -11,6 +11,7 @@ #include "mozilla/dom/Promise.h" #include "mozilla/dom/telephony/TelephonyCommon.h" +#include "nsIAudioChannelAgent.h" #include "nsITelephonyCallInfo.h" #include "nsITelephonyService.h" @@ -31,6 +32,7 @@ class TelephonyDialCallback; class OwningTelephonyCallOrTelephonyCallGroup; class Telephony final : public DOMEventTargetHelper, + public nsIAudioChannelAgentCallback, private nsITelephonyListener { /** @@ -44,6 +46,8 @@ class Telephony final : public DOMEventTargetHelper, friend class telephony::TelephonyDialCallback; + // The audio agent is needed to communicate with the audio channel service. + nsCOMPtr mAudioAgent; nsCOMPtr mService; nsRefPtr mListener; @@ -54,8 +58,13 @@ class Telephony final : public DOMEventTargetHelper, nsRefPtr mReadyPromise; + bool mIsAudioStartPlaying; + + uint32_t mAudioAgentNotify; + public: NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIAUDIOCHANNELAGENTCALLBACK NS_DECL_NSITELEPHONYLISTENER NS_REALLY_FORWARD_NSIDOMEVENTTARGET(DOMEventTargetHelper) NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Telephony, @@ -94,6 +103,15 @@ public: void StopTone(const Optional& aServiceId, ErrorResult& aRv); + // In the audio channel architecture, the system app needs to know the state + // of every audio channel, including the telephony. Therefore, when a + // telephony call is activated , the audio channel service would notify the + // system app about that. And we need a agent to communicate with the audio + // channel service. We would follow the call states to make a correct + // notification. + void + OwnAudioChannel(ErrorResult& aRv); + bool GetMuted(ErrorResult& aRv) const; @@ -213,6 +231,10 @@ private: nsresult HandleCallInfo(nsITelephonyCallInfo* aInfo); + + // Check the call states to decide whether need to send the notificaiton. + nsresult + HandleAudioAgentState(); }; } // namespace dom diff --git a/dom/telephony/TelephonyCall.cpp b/dom/telephony/TelephonyCall.cpp index cfe87cbb6397..d1ab58f7d49f 100644 --- a/dom/telephony/TelephonyCall.cpp +++ b/dom/telephony/TelephonyCall.cpp @@ -324,34 +324,10 @@ TelephonyCall::Hold(ErrorResult& aRv) return nullptr; } - if (mCallState != nsITelephonyService::CALL_STATE_CONNECTED) { - NS_WARNING(nsPrintfCString("Hold non-connected call is rejected!" - " (State: %u)", mCallState).get()); - promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); - return promise.forget(); - } - - if (mGroup) { - NS_WARNING("Hold a call in conference is rejected!"); - promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); - return promise.forget(); - } - - if (!mSwitchable) { - NS_WARNING("Hold a non-switchable call is rejected!"); - promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); - return promise.forget(); - } - nsCOMPtr callback = new TelephonyCallback(promise); - aRv = mTelephony->Service()->HoldCall(mServiceId, mCallIndex, callback); - NS_ENSURE_TRUE(!aRv.Failed(), nullptr); - - if (mSecondId) { - // No state transition when we switch two numbers within one TelephonyCall - // object. Otherwise, the state here will be inconsistent with the backend - // RIL and will never be right. - return promise.forget(); + aRv = Hold(callback); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; } return promise.forget(); @@ -365,28 +341,77 @@ TelephonyCall::Resume(ErrorResult& aRv) return nullptr; } - if (mCallState != nsITelephonyService::CALL_STATE_HELD) { - NS_WARNING(nsPrintfCString("Resume non-held call is rejected!" + nsCOMPtr callback = new TelephonyCallback(promise); + aRv = Resume(callback); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + return promise.forget(); +} + +nsresult +TelephonyCall::Hold(nsITelephonyCallback* aCallback) +{ + if (mCallState != nsITelephonyService::CALL_STATE_CONNECTED) { + NS_WARNING(nsPrintfCString("Hold non-connected call is rejected!" " (State: %u)", mCallState).get()); - promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); - return promise.forget(); + aCallback->NotifyError(NS_LITERAL_STRING("InvalidStateError")); + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + if (mGroup) { + NS_WARNING("Hold a call in conference is rejected!"); + aCallback->NotifyError(NS_LITERAL_STRING("InvalidStateError")); + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + if (!mSwitchable) { + NS_WARNING("Hold a non-switchable call is rejected!"); + aCallback->NotifyError(NS_LITERAL_STRING("InvalidStateError")); + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + nsresult rv = mTelephony->Service()->HoldCall(mServiceId, mCallIndex, aCallback); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (mSecondId) { + // No state transition when we switch two numbers within one TelephonyCall + // object. Otherwise, the state here will be inconsistent with the backend + // RIL and will never be right. + return NS_OK; + } + + return NS_OK; +} + +nsresult +TelephonyCall::Resume(nsITelephonyCallback* aCallback) +{ + if (mCallState != nsITelephonyService::CALL_STATE_HELD) { + NS_WARNING("Resume non-held call is rejected!"); + aCallback->NotifyError(NS_LITERAL_STRING("InvalidStateError")); + return NS_ERROR_DOM_INVALID_STATE_ERR; } if (mGroup) { NS_WARNING("Resume a call in conference is rejected!"); - promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); - return promise.forget(); + aCallback->NotifyError(NS_LITERAL_STRING("InvalidStateError")); + return NS_ERROR_DOM_INVALID_STATE_ERR; } if (!mSwitchable) { NS_WARNING("Resume a non-switchable call is rejected!"); - promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); - return promise.forget(); + aCallback->NotifyError(NS_LITERAL_STRING("InvalidStateError")); + return NS_ERROR_DOM_INVALID_STATE_ERR; } - nsCOMPtr callback = new TelephonyCallback(promise); - aRv = mTelephony->Service()->ResumeCall(mServiceId, mCallIndex, callback); - NS_ENSURE_TRUE(!aRv.Failed(), nullptr); + nsresult rv = mTelephony->Service()->ResumeCall(mServiceId, mCallIndex, aCallback); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } - return promise.forget(); -} + return NS_OK; +} \ No newline at end of file diff --git a/dom/telephony/TelephonyCall.h b/dom/telephony/TelephonyCall.h index dbb53f444bb9..4d4bae031de2 100644 --- a/dom/telephony/TelephonyCall.h +++ b/dom/telephony/TelephonyCall.h @@ -12,6 +12,7 @@ #include "mozilla/dom/TelephonyCallBinding.h" #include "mozilla/dom/TelephonyCallId.h" #include "mozilla/dom/telephony/TelephonyCommon.h" +#include "nsITelephonyService.h" class nsPIDOMWindow; @@ -185,6 +186,12 @@ private: ~TelephonyCall(); + nsresult + Hold(nsITelephonyCallback* aCallback); + + nsresult + Resume(nsITelephonyCallback* aCallback); + void ChangeStateInternal(uint16_t aCallState, bool aFireEvents); diff --git a/dom/telephony/TelephonyCallGroup.cpp b/dom/telephony/TelephonyCallGroup.cpp index 541cc134a79b..addfe94cfa48 100644 --- a/dom/telephony/TelephonyCallGroup.cpp +++ b/dom/telephony/TelephonyCallGroup.cpp @@ -347,16 +347,12 @@ TelephonyCallGroup::Hold(ErrorResult& aRv) return nullptr; } - if (mCallState != nsITelephonyService::CALL_STATE_CONNECTED) { - NS_WARNING("Holding a non-connected call is rejected!"); - promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); - return promise.forget(); + nsCOMPtr callback = new TelephonyCallback(promise); + aRv = Hold(callback); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; } - nsCOMPtr callback = new TelephonyCallback(promise); - aRv = mTelephony->Service()->HoldConference(mCalls[0]->ServiceId(), - callback); - NS_ENSURE_TRUE(!aRv.Failed(), nullptr); return promise.forget(); } @@ -370,15 +366,47 @@ TelephonyCallGroup::Resume(ErrorResult& aRv) return nullptr; } - if (mCallState != nsITelephonyService::CALL_STATE_HELD) { - NS_WARNING("Resuming a non-held call is rejected!"); - promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); - return promise.forget(); + nsCOMPtr callback = new TelephonyCallback(promise); + aRv = Resume(callback); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; } - nsCOMPtr callback = new TelephonyCallback(promise); - aRv = mTelephony->Service()->ResumeConference(mCalls[0]->ServiceId(), - callback); - NS_ENSURE_TRUE(!aRv.Failed(), nullptr); return promise.forget(); } + +nsresult +TelephonyCallGroup::Hold(nsITelephonyCallback* aCallback) +{ + if (mCallState != nsITelephonyService::CALL_STATE_CONNECTED) { + NS_WARNING("Holding a non-connected call is rejected!"); + aCallback->NotifyError(NS_LITERAL_STRING("InvalidStateError")); + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + nsresult rv = mTelephony->Service()->HoldConference(mCalls[0]->ServiceId(), + aCallback); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult +TelephonyCallGroup::Resume(nsITelephonyCallback* aCallback) +{ + if (mCallState != nsITelephonyService::CALL_STATE_HELD) { + NS_WARNING("Resuming a non-held call is rejected!"); + aCallback->NotifyError(NS_LITERAL_STRING("InvalidStateError")); + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + nsresult rv = mTelephony->Service()->ResumeConference(mCalls[0]->ServiceId(), + aCallback); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} diff --git a/dom/telephony/TelephonyCallGroup.h b/dom/telephony/TelephonyCallGroup.h index 38c1ba1474cd..8e473b919951 100644 --- a/dom/telephony/TelephonyCallGroup.h +++ b/dom/telephony/TelephonyCallGroup.h @@ -30,6 +30,8 @@ public: NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(TelephonyCallGroup, DOMEventTargetHelper) + friend class Telephony; + nsPIDOMWindow* GetParentObject() const { @@ -108,6 +110,12 @@ private: explicit TelephonyCallGroup(nsPIDOMWindow* aOwner); ~TelephonyCallGroup(); + nsresult + Hold(nsITelephonyCallback* aCallback); + + nsresult + Resume(nsITelephonyCallback* aCallback); + nsresult NotifyCallsChanged(TelephonyCall* aCall); diff --git a/dom/webidl/Telephony.webidl b/dom/webidl/Telephony.webidl index 5eb460b74bbc..19fd9dd80c41 100644 --- a/dom/webidl/Telephony.webidl +++ b/dom/webidl/Telephony.webidl @@ -49,6 +49,12 @@ interface Telephony : EventTarget { [Throws] void stopTone(optional unsigned long serviceId); + // Calling this method, the app will be treated as owner of the telephony + // calls from the AudioChannel policy. + [Throws, + CheckAllPermissions="audio-channel-telephony"] + void ownAudioChannel(); + [Throws] attribute boolean muted;